DI(IOC) 何謂DI(IOC) DI(依賴注入)是spring的核心功能之一。 Dependency Injection 和 Inversion of Control 其實(shí)就是一個(gè)東西的兩種不同的說(shuō)法而已,。本質(zhì)上是一回事,。Dependency Injection 是一個(gè)程序設(shè)計(jì)模式和架構(gòu)模型, 一些時(shí)候也稱作Inversion of Control,,盡管在技術(shù)上來(lái)講,,Dependency Injection 是一個(gè) Inversion of Control 的特殊實(shí)現(xiàn),Dependency Injection 是指一個(gè)對(duì)象應(yīng)用另外一個(gè)對(duì)象來(lái)提供一個(gè)特殊的能力,,例如:把一個(gè)數(shù)據(jù)庫(kù)連接以參數(shù)的形式傳到一個(gè)對(duì)象的結(jié)構(gòu)方法里面而不是在那個(gè)對(duì)象內(nèi)部自行創(chuàng)建一個(gè)連接,。Inversion of Control 和 Dependency Injection 的基本思想就是把類的依賴從類內(nèi)部轉(zhuǎn)化到外部以減少依賴。 應(yīng)用Inversion of Control,,對(duì)象在被創(chuàng)建的時(shí)候,,由一個(gè)調(diào)控系統(tǒng)內(nèi)所有對(duì)象的外界實(shí)體,將其所依賴的對(duì)象的引用,,傳遞給它,。也可以說(shuō),依賴被注入到對(duì)象中,。所以,,Inversion of Control 是,關(guān)于一個(gè)對(duì)象如何獲取他所依賴的對(duì)象的引用,,這個(gè)責(zé)任的反轉(zhuǎn),。IoC是通過(guò)處理對(duì)象定義依賴的方式來(lái)工作,也就是說(shuō),,一起協(xié)作的對(duì)象,,要么通過(guò)構(gòu)造函數(shù)參數(shù)來(lái)獲得,,要么在構(gòu)造之后給對(duì)象設(shè)置屬性來(lái)獲得,要么從工廠方法返回的方式來(lái)獲得,。容器先創(chuàng)建bean,,然后再注入這些依賴。這個(gè)獲取過(guò)程是完全反過(guò)來(lái)的,,所以命名為控制反轉(zhuǎn)(IoC),。 DI能夠刪除任何特定的依賴于別的類或第三方接口的類,并且能夠在初始化構(gòu)造時(shí)加載要依賴的類,。DI的優(yōu)點(diǎn)是你可以依賴類的實(shí)現(xiàn)而并不需要更改你的代碼,。你甚至可以在接口不變的條件下重寫依賴的實(shí)現(xiàn)而不用改變你的編碼,即面向接口的編程,。 文末有資料分享,,感謝讀者的閱讀。為了方便記憶,,我整理了一份腦圖,。
小編整理了一些資深架構(gòu)師們精講的資料,(包括高可用,,高并發(fā),,spring源碼,mybatis源碼,,JVM,,大數(shù)據(jù),Netty等多個(gè)技術(shù)知識(shí)的架構(gòu)視頻資料和各種電子書(shū)籍閱讀)視頻資料獲取方式幫忙 轉(zhuǎn)發(fā) 轉(zhuǎn)發(fā) 轉(zhuǎn)發(fā) 后關(guān)注我私信回復(fù)“架構(gòu)”領(lǐng)??! 構(gòu)造器注入:通過(guò)構(gòu)造器注入,,能使當(dāng)前實(shí)例作為不可變對(duì)象,,并且能確保所有需要的依賴都是非空的.更進(jìn)一步,構(gòu)造器注入返回給客戶代碼的是一個(gè)完全初始化狀態(tài)的對(duì)象.Setter方法注入:Setter方法注入作為構(gòu)造器注入的補(bǔ)充實(shí)現(xiàn).能注入可選的有默認(rèn)值的依賴.否則,,會(huì)隨處校驗(yàn)依賴的非空與否.自動(dòng)裝配@Autowired:即通過(guò)注解自動(dòng)裝配,,默認(rèn)方式是byType.@Resource:即通過(guò)注解自動(dòng)裝配,默認(rèn)方式是[email protected]:類似與@Autowired@Qualifier:指定實(shí)現(xiàn)不同的限定符,,在具體注入時(shí),,通過(guò)該注解具體限定 Spring容器會(huì)在容器加載時(shí)校驗(yàn)依賴非空和循環(huán)依賴.在初始化Bean時(shí),Spring會(huì)在bean真正創(chuàng)建之前盡可能晚的設(shè)置屬性和解決依賴關(guān)系. bean bean定義和依賴實(shí)現(xiàn)方式 XML文本配置文件 通過(guò)上面的方式來(lái)定義一個(gè)bean,,通過(guò)在bean中添加依賴來(lái)達(dá)到目的. 主要依賴方式有: 構(gòu)造器注入.Setter屬性方法注入 通過(guò)XML配置文件構(gòu)造Spring beans和依賴缺失了編譯時(shí)的類型檢查,,比如構(gòu)造器參數(shù)的類型錯(cuò)誤,甚至是構(gòu)造器錯(cuò)誤的參數(shù)只有在ApplicationContext容器在運(yùn)行時(shí)構(gòu)造時(shí)才會(huì)檢查,。 使用注解 通過(guò)配置自動(dòng)注解掃描的根包,,并且在bean上使用注解@Component(@Service,@Repositoty,,@javax.inject.Named)等標(biāo)示他是一個(gè)bean. 通過(guò)如上配置自動(dòng)掃描根包下的類,并作自動(dòng)注解綁定 解釋下< context:annotation-config/>: 它的作用是隱式的向Spring容器注冊(cè) AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor, RequiredAnnotationBeanPostProcessor 這4個(gè)BeanPostProcessor.注冊(cè)這4個(gè)bean處理器主要的作用是為了你的系統(tǒng)能夠識(shí)別相應(yīng)的注解,。如果想使用@Autowired,@PersistenceContext,@Required,@Resource,@PostConstruct,@PreDestroy,就需要按照傳統(tǒng)聲明一條一條去聲明注解Bean,,就會(huì)顯得十分繁瑣.因此如果在Spring的配置文件中事先加上這樣一條配置的話,那么所有注解的傳統(tǒng)聲明就可以被忽略,,即不用在寫傳統(tǒng)的聲明,,Spring會(huì)自動(dòng)完成聲明。 解釋下: 作用是讓Bean定義注解工作起來(lái),也就是上述傳統(tǒng)聲明方式.它的base-package屬性指定了需要掃描的類包,,類包及其遞歸子包中所有的類都會(huì)被處理,。值得注意的是不但啟用了對(duì)類包進(jìn)行掃描以實(shí)施注釋驅(qū)動(dòng) Bean 定義的功能,同時(shí)還啟用了注釋驅(qū)動(dòng)自動(dòng)注入的功能(即還隱式地在內(nèi)部注冊(cè)了AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor),,因此當(dāng)使用后,,就可以將移除了。 @Autowiredprivate UserService userService; @Service("userService")public class UserServiceImpl implements UserService 通過(guò)如上配置定義一個(gè)bean,,并作自動(dòng)綁定 主要注解依賴方式有: @Autowired:即通過(guò)注解自動(dòng)裝配,,默認(rèn)方式是byType.@Resource:即通過(guò)注解自動(dòng)裝配,默認(rèn)方式是[email protected]:類似與@Autowired 當(dāng)通過(guò)@Autowired注入時(shí),,默認(rèn)是通過(guò)類型匹配具體的實(shí)現(xiàn)類的,,但是如果接口有多個(gè)實(shí)現(xiàn)類,Spring容器是沒(méi)法做選擇的,,有兩種方式解決這個(gè)問(wèn)題: @Primary注解,,指定當(dāng)有多個(gè)候選實(shí)現(xiàn)時(shí),首選這個(gè)實(shí)現(xiàn).2.@Qualifier注解指定不同實(shí)現(xiàn)不同的限定符,,在具體注入時(shí),,通過(guò)該注解具體限定. 解釋下@Autowired: 可以對(duì)成員變量、方法和構(gòu)造函數(shù)進(jìn)行標(biāo)注,,來(lái)完成自動(dòng)裝配的工作,。@Autowired的標(biāo)注位置不同。它們都會(huì)在Spring在初始化這個(gè)bean時(shí),,自動(dòng)裝配這個(gè)屬性,。注解之后就不需要set/get方法了。 其中@Inject 和@Named是JSR 330 Standard Annotations.s 通過(guò)Spring提供的擴(kuò)展方式做處理 可以通過(guò)init-method的方式來(lái)實(shí)現(xiàn)初始化注入,,還可以通過(guò)實(shí)現(xiàn)``InitializingBean`接口來(lái)實(shí)現(xiàn),,但此種方式對(duì)業(yè)務(wù)代碼有侵入性,少用,。bean加載過(guò)程可以通過(guò)設(shè)置factory-method的方式設(shè)置工廠方法,,來(lái)設(shè)置一些靜態(tài)屬性調(diào)用getter方法,用工廠Bean PropertyPathFactoryBean調(diào)用普通方法(實(shí)例方法或者類方法),,用工廠Bean MethodInvokingFactoryBean獲取Field的值,,用工廠Bean FieldRetrievingFactoryBean @Configuration&@Bean @Bean可以出現(xiàn)在@Configurationor@Component,,其中@Configuration類似于xml中的,而@Component類似于xml中的,@Component可以作為@Configuration的替代。 但是有一些問(wèn)題:當(dāng)我們使用@Bean注解在例如@Component作用的class里面時(shí),,將會(huì)發(fā)生一種稱之為注解@Bean的lite mode出現(xiàn),,這種不會(huì)使用CGLIB代理.所以只要我在@Bean修飾的方法之間不相互編碼調(diào)用,代碼將會(huì)很好的運(yùn)作. 下面是@Bean的lite mode示例: @Component public class ConfigInComponent { @Bean public SimpleBean simpleBean() { return new SimpleBean(); } @Bean public SimpleBeanConsumer simpleBeanConsumer() { return new SimpleBeanConsumer(simpleBean()); } } 上述代碼在new SimpleBeanConsumer(simpleBean())這一步實(shí)例化bean時(shí),,不會(huì)將第一步@Bean實(shí)例化的bean自動(dòng)注入到simpleBeanConsumerbean中,,而是重新用simpleBean(),生成一個(gè)新的SimpleBean 實(shí)例.而@Configuration則不會(huì)發(fā)生上述情況,代碼如下: @Configuration public class ConfigInConfiguration { @Bean public SimpleBean simpleBean() { return new SimpleBean(); } @Bean public SimpleBeanConsumer simpleBeanConsumer() { return new SimpleBeanConsumer(simpleBean()); } } 要改善上述問(wèn)題,,可以通過(guò)以下方式實(shí)現(xiàn): @Component public class ConfigInComponent { @Autowired SimpleBean simpleBean; @Bean public SimpleBean simpleBean() { return new SimpleBean(); } @Bean public SimpleBeanConsumer simpleBeanConsumer() { return new SimpleBeanConsumer(simpleBean); } } 通過(guò)將@Bean生成的bean Autowired到屬性上,,并在@Bean實(shí)例化SimpleBeanConsumerbean時(shí)傳入此屬性,來(lái)達(dá)到目的. 參考: Spring @Configuration vs @Component bean生命周期 bean的作用域 sessionrequestprototypesingletonapplication 其中singleton是容器級(jí)別的,,即一個(gè)容器一個(gè)bean實(shí)例,spring的單例實(shí)例緩存在ConcurrentHashMap中;而GOF的單例模式是基于ClassLoader的,,即一個(gè)類加載器只能有一個(gè)實(shí)例 通過(guò)bean后處理來(lái)增強(qiáng)功能 BeanFactoryPostProcessor 通過(guò)實(shí)現(xiàn)BeanFactoryPostProcessor,對(duì)bean配置的元數(shù)據(jù)做一些處理(可以改變初始化bean的內(nèi)容),,比如為安全考慮的數(shù)據(jù)庫(kù)密碼加密配置在配置文件中,,在jdbc連接數(shù)據(jù)庫(kù)時(shí)需要解密可以通過(guò)擴(kuò)展BeanFactoryPostProcessor來(lái)實(shí)現(xiàn). @Componentpublic class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor, Ordered { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { BeanDefinition definition=beanFactory.getBeanDefinition("xmlBeanDefinition"); MutablePropertyValues propertyValues=definition.getPropertyValues(); if (propertyValues.contains("name")) { PropertyValue property=propertyValues.getPropertyValue("name"); String name=((TypedStringValue) property.getValue()).getValue(); propertyValues.add("name",name.replace(" ","")); } if (propertyValues.contains("age")) { PropertyValue property=propertyValues.getPropertyValue("age"); Double age=Double.parseDouble(((TypedStringValue) property.getValue()).getValue()); propertyValues.add("age",Math.round(age)); } } @Override public int getOrder() { return 3; } } 如上,可以通過(guò)實(shí)現(xiàn)Ordered或者注解@Order的方式來(lái)指定加載順序. BeanPostProcessor 通過(guò)實(shí)現(xiàn)BeanPostProcessor,,可以實(shí)現(xiàn)在Spring容器在完成Bean的實(shí)例化,,配置和其他的初始化前后做一些自己的業(yè)務(wù)處理,比如我們可以統(tǒng)計(jì)自定義的的Bean集合. @Componentpublic class CustomBeanPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { if(beanName.equals("userService")){ UserService ds=(UserService) bean; System.out.println(ds); } return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if(beanName.equals("userService")){ UserService ds=(UserService) bean; System.out.println(ds); } return bean; } } AOP(面向切面的編程) AOP(面向切面的編程)是通過(guò)劃分關(guān)注點(diǎn),,來(lái)做一些事,,這些事在實(shí)際編程中與整體業(yè)務(wù)無(wú)關(guān),比如事務(wù)控制,,日志管理等,通過(guò)aop將其與業(yè)務(wù)代碼解耦,,以實(shí)現(xiàn)精簡(jiǎn)的業(yè)務(wù)編碼.其整體描述為在什么時(shí)候做什么事. AOP三要素: 方面(Aspect)--多個(gè)通知和切點(diǎn)的集合切入點(diǎn)(Pointcut)--在什么地方通知(增強(qiáng)處理)(Advice)--在什么時(shí)候干什么事比如有MethodInterceptor,AfterAdvice,BeforeAdvice等.描述的是在什么時(shí)候做一些增強(qiáng)處理. 在編程配置中,如下: <aop:pointcut< p=""> expression="(execution(public * com.earthlyfish.service..get*(..))) or (execution(public * com.earthlyfish.service..find*(..)))" id="methodCachePoint" /> :用來(lái)定義切入點(diǎn),,該切入點(diǎn)可以重用 :用來(lái)定義只有一個(gè)通知和一個(gè)切入點(diǎn)的切面 ?。河脕?lái)定義切面,該切面可以包含多個(gè)切入點(diǎn)和通知,,而且標(biāo)簽內(nèi)部的通知和切入點(diǎn)定義是無(wú)序的,;和advisor的區(qū)別就在此,,advisor只包含一個(gè)通知和一個(gè)切入點(diǎn) 事務(wù)管理 Spring通過(guò)TransactionManager來(lái)實(shí)現(xiàn)事務(wù)管理,,現(xiàn)有兩種方式,一種是通過(guò)aop注入式的方式實(shí)現(xiàn),,另一種是通過(guò)@Transactional在方法上實(shí)現(xiàn)事務(wù)管理. aop注入式 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <tx:method name="create*" propagation="REQUIRED" rollback-for="java.lang.Exception,java.lang.RuntimeException" /> <tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception,java.lang.RuntimeException" /> <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Exception,java.lang.RuntimeException" /> <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception,java.lang.RuntimeException" /> <tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception,java.lang.RuntimeException" /> <tx:method name="remove*" propagation="REQUIRED" rollback-for="java.lang.Exception,java.lang.RuntimeException" /> <aop:pointcut id="module-pointcut" expression="execution(* com.wyp.module.service.*.*(..))" /> @Transactional 需要在配置文件中有tx相關(guān)的注解. 在需要的地方通過(guò)@Transactional注入: @Transactional(propagation=Propagation.REQUIRED) public UserType registerUser(Customer customer) { 對(duì)于這種方式,,如果一個(gè)類里面有兩個(gè)方法A和B,A方法調(diào)用了B方法,,如果調(diào)用A方法的話,整個(gè)事務(wù)運(yùn)用會(huì)有下面三種情況: A上加了注解,,B上不加,,則整體會(huì)受事務(wù)控制.A上加了注解,B上加,,則整體會(huì)受事務(wù)控制.其實(shí)這里調(diào)用和1一樣,,在AOP代理時(shí)只給A方法加了環(huán)繞事務(wù)通知.A上不加注解,B上加,,則整體不受事務(wù)控制.這是因?yàn)檎{(diào)用A方法時(shí)由于沒(méi)有判斷到事務(wù)注解的存在,,因此代理類沒(méi)有聲稱事務(wù)控制的字節(jié)碼,這直接使用的被代理類的字節(jié)碼,,所以不受事務(wù)控制. 編碼式 編碼式通過(guò)在代碼中加入事務(wù)管理來(lái)達(dá)到目的,,但由于事務(wù)邏輯與主業(yè)務(wù)邏輯沒(méi)啥關(guān)系,代碼沒(méi)必要緊耦合在一起,,所以此方法不怎么使用. JPA知識(shí)點(diǎn) mapping pojo結(jié)構(gòu)詳述 啥事不干,,先上一個(gè)多對(duì)多關(guān)系的例子. @Entity@Table(name="t_role")public class Role { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @Column(name="role_name") private String roleName; @Column(name="role_description") private String roleDescription; @Column private boolean enabled; //角色下的所用用戶 @JsonIgnore @ManyToMany(mappedBy="roles") private Set users=new HashSet<>(); @Entity@Table(name="t_user")public class User implements Serializable, Cloneable { /** * 靜態(tài)long類型常量serialVersionUID的作用: * * 顯示的設(shè)置serialVersionUID值就可以保證版本的兼容性,如果你在類中寫上了這個(gè)值,,就算類變動(dòng)了,, * 它反序列化的時(shí)候也能和文件中的原值匹配上。而新增的值則會(huì)設(shè)置成零值,,刪除的值則不會(huì)顯示,。 */ private static final long serialVersionUID=-8220100956296048447L; @Id @GeneratedValue private Long id; @Column(unique=true) private String name; @Column(length=32) private String password; @Column(length=32) private String salt; @Column(precision=3) private int age; /** * 關(guān)系維護(hù)端,負(fù)責(zé)多對(duì)多關(guān)系的維護(hù) * * @JoinTable 表示關(guān)聯(lián)表的信息,,其中: * 1.name 表示關(guān)聯(lián)表的名字 * 2.joinColumns 指定外鍵的名字,,關(guān)聯(lián)到關(guān)系維護(hù)端Role * 3.inverseJoinColumns 指定外鍵的名字,要關(guān)聯(lián)的關(guān)系被維護(hù)端 * 以上完全可以默認(rèn),,默認(rèn)情況下: * 1.name 主表名_從表名 * 2.joinColumns 主表_id * 3.inverseJoinColumns 從表_id */ @ManyToMany @JoinTable(name="t_user_role", joinColumns=@JoinColumn(name="user_id"),inverseJoinColumns=@JoinColumn(name="role_id")) private Set roles=new HashSet<>(); 上述兩端代碼描述的是Role和User的多對(duì)多關(guān)系. 注解說(shuō)明 mappedBymappedBy和inverse的作用是相同的,,只不過(guò)inverse用在xml文件中,表示的意思是在關(guān)系雙方都有關(guān)系維護(hù)任務(wù)時(shí),,以哪一方為主導(dǎo),,即沒(méi)有被mappedBy修飾的一方維護(hù).即外鍵將儲(chǔ)存在沒(méi)有被mappedBy修飾一方.@Temporal@Temporal是將java.sql.*下的Data等轉(zhuǎn)成java.util.*包下的Date.@Transient@Transient標(biāo)記該字段不記錄到數(shù)據(jù)結(jié)構(gòu) 初始化數(shù)據(jù)結(jié)構(gòu)和數(shù)據(jù) 可以通過(guò)設(shè)置hibernate.hbm2ddl.auto的值來(lái)達(dá)到初始化數(shù)據(jù)結(jié)構(gòu)的目的,有以下幾個(gè)值. create : 每次加載JPA,,重新創(chuàng)建數(shù)據(jù)庫(kù)表結(jié)構(gòu),,這就是導(dǎo)致數(shù)據(jù)庫(kù)表數(shù)據(jù)丟失的原因create-drop :加載JPA時(shí)創(chuàng)建,退出是刪除表結(jié)構(gòu)update :加載JPA自動(dòng)更新數(shù)據(jù)庫(kù)結(jié)構(gòu)none/validate :加載JPA時(shí),,驗(yàn)證創(chuàng)建數(shù)據(jù)庫(kù)表結(jié)構(gòu),當(dāng)然none時(shí)不做任何操作 在本機(jī)開(kāi)發(fā)調(diào)試初始化數(shù)據(jù)的時(shí)候可以選擇create,、update等。 但是應(yīng)用發(fā)布正式版本的時(shí)候,,對(duì)數(shù)據(jù)庫(kù)現(xiàn)有的數(shù)據(jù)或表結(jié)構(gòu)進(jìn)行自動(dòng)的更新是很危險(xiǎn)的,。此時(shí)此刻應(yīng)該由DBA同志通過(guò)手工的方式進(jìn)行后臺(tái)的數(shù)據(jù)庫(kù)操作。 hibernate.hbm2ddl.auto的值建議是none或validate。validate應(yīng)該是最好的選擇:這樣 spring在加載之初,,如果model層和數(shù)據(jù)庫(kù)表結(jié)構(gòu)不同,,就會(huì)報(bào)錯(cuò),這樣有助于技術(shù)運(yùn)維預(yù)先發(fā)現(xiàn)問(wèn)題,。 當(dāng)然我們可以通過(guò)自定義初始化腳本的方式來(lái)實(shí)現(xiàn)初始化數(shù)據(jù): 通過(guò)data: classpath:/database/import.sql的方式來(lái)實(shí)現(xiàn)--Spring boot親測(cè)通過(guò) 遇到的問(wèn)題 Spring MVC轉(zhuǎn)換JPA多對(duì)多對(duì)象的json時(shí),,無(wú)限循環(huán)問(wèn)題在使用JPA處理多對(duì)多關(guān)系時(shí),發(fā)生了無(wú)限循環(huán)的問(wèn)題,,證書(shū)代碼如下: @Entity @Table(name="t_menu") public class Menu implements Comparable { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String menuName; private String menuDesc; private int priority; private String staticIndex; private int parantId; private boolean enabled; @Transient private List children; //菜單所屬role @ManyToMany(mappedBy="roleMenus", fetch=FetchType.LAZY) private Set roles=new HashSet<>(); //菜單所屬role @ManyToMany(mappedBy="userMenus", fetch=FetchType.LAZY) private Set users=new HashSet<>(); } 上面轉(zhuǎn)換成json數(shù)據(jù)時(shí),,出現(xiàn)無(wú)限循環(huán)以致棧溢出.針對(duì)以上問(wèn)題可以通過(guò)以下注解實(shí)現(xiàn):使用如下: //菜單所屬role @JsonIgnore @ManyToMany(mappedBy="roleMenus", fetch=FetchType.LAZY) private Set roles=new HashSet<>(); //菜單所屬role @JsonBackReference @ManyToMany(mappedBy="userMenus", fetch=FetchType.LAZY) private Set users=new HashSet<>(); @JsonIgnore json轉(zhuǎn)換時(shí)忽略某個(gè)屬性,以斷開(kāi)無(wú)限遞歸,,序列化和反序列化均忽略,,可以用在字段或get(序列化),set(反序列化)方法上@JsonBackReference json轉(zhuǎn)換時(shí)忽略某個(gè)屬性,以斷開(kāi)無(wú)限遞歸,,序列化時(shí)忽略,,可以用在字段或get(序列化),set(反序列化)方法上,序列化時(shí),相當(dāng)于@JsonIgnore@JsonManagedReference json轉(zhuǎn)換時(shí)會(huì)被序列化,,反序列化時(shí),,如果沒(méi)有該注解,則不會(huì)自動(dòng)注入@JsonBackReference標(biāo)注的屬性 spring mvc Configuring a servlet container 通過(guò)配置文件實(shí)現(xiàn) 通過(guò) org.springframework.web.servlet.DispatcherServlet管理,,在web.xml里面配置: rest org.springframework.web.servlet.DispatcherServlet contextConfigLocation /WEB-INF/rest-servlet.xml 1 rest / 如果 contextConfigLocation /WEB-INF/rest-servlet.xml 不指定,,則默認(rèn)會(huì)加載該文件WEB-INF/[servlet-name]-servlet.xml. 通過(guò)這個(gè)web.xml,引出了另一個(gè)問(wèn)題,,即web.xml文件的加載順序是什么樣的,? 通過(guò)查看tomcat源碼,tomcat加載web.xml的順序是: context-param ---> listener ---> filter ---> servlet 首先tomcat會(huì)生成一個(gè)程序應(yīng)用級(jí)ServletContext,全局唯一,,其中將context-param放在第一位主要是listener和filter會(huì)用到配置的初始化參數(shù),, 比如Spring配置的contextConfigLocation,在ContextLoaderLister加載時(shí)會(huì)從ServletContext的初始化參數(shù)中獲取配置文件,進(jìn)行bean的初始化操作. 上面還有一點(diǎn),,那就是servlet的加載,,當(dāng)load-on-startup大于等于0時(shí),表示在tomcat容器啟動(dòng)時(shí)加載這個(gè)Servlet,否則,,在第一次使用時(shí)才加載. 解釋下 是一種簡(jiǎn)寫形式,,完全可以手動(dòng)配置替代這種簡(jiǎn)寫形式。會(huì)自動(dòng)注冊(cè)DefaultAnnotationHandlerMapping與AnnotationMethodHandlerAdapter 兩個(gè)bean,是spring MVC為@Controllers分發(fā)請(qǐng)求所必須的,。 零配置實(shí)現(xiàn) 從servlet3.0開(kāi)始,,web支持no web.xml實(shí)現(xiàn)容器的初始化工作,是得于 javax.servlet.ServletContainerInitializer對(duì)初始化工作的支持. spring通過(guò)實(shí)現(xiàn) javax.servlet.ServletContainerInitializer,,重寫onStartUp方法,來(lái)提供無(wú)xml文件的spring容器初始化工作.下面是一個(gè)spring實(shí)現(xiàn)初始化的例子. import java.util.EnumSet;import javax.servlet.DispatcherType;import javax.servlet.ServletContext;import javax.servlet.ServletException;import org.springframework.web.WebApplicationInitializer;import org.springframework.web.context.ContextLoaderListener;import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;import org.springframework.web.filter.CharacterEncodingFilter;import cn.oracle.action.AppConfig;public class DefaultConfigration implements WebApplicationInitializer { @Override public void onStartup(ServletContext container) throws ServletException { // 配置Spring提供的字符編碼過(guò)濾器 javax.servlet.FilterRegistration.Dynamic filter=container.addFilter("encoding", new CharacterEncodingFilter()); // 配置過(guò)濾器的過(guò)濾路徑 filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/"); // 基于注解配置的Spring容器上下文 AnnotationConfigWebApplicationContext rootContext=new AnnotationConfigWebApplicationContext(); // 注冊(cè)Spring容器配置類 rootContext.register(AppConfig.class); container.addListener(new ContextLoaderListener(rootContext)); } } 具體的AppConfig相當(dāng)于原來(lái)的application-*.xml等spring的配置文件處理.如下: import org.springframework.context.annotationponentScan;import org.springframework.context.annotation.Configuration;@Configuration@ComponentScan(basePackages={"cn.oracle.*"})public class AppConfig { } 可以再此基礎(chǔ)上擴(kuò)展,,編寫一個(gè)no without xml的app. 從上可以看到WebApplicationInitializer是一個(gè)接口,和 ServletContainerInitializer沒(méi)有關(guān)系,,下面看一下具體的細(xì)節(jié). 下面這一段代碼是spring做處理的過(guò)程. @HandlesTypes({WebApplicationInitializer.class})public class SpringServletContainerInitializer implements ServletContainerInitializer{ public void onStartup(Set<class> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List initializers=new LinkedList(); if (webAppInitializerClasses !=null) { for (Class waiClass : webAppInitializerClasses) { if ((!waiClass.isInterface()) && (!Modifier.isAbstract(waiClass.getModifiers())) && (WebApplicationInitializer.class .isAssignableFrom(waiClass))) { try { initializers.add((WebApplicationInitializer)waiClass.newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } 可以看到 SpringServletContainerInitializer實(shí)現(xiàn)了 SpringServletContainerInitializer,而spring通過(guò)注解@HandlesTypes({ WebApplicationInitializer.class})來(lái)實(shí)現(xiàn)掃描WebApplicationInitializer該類,,并將其注入到Set集. 注 :@HandlesTypes is used to declare the class types that aServletContainerInitializer can handle. 一些鏈接: 基于純Java代碼的Spring容器和Web容器零配置的思考和實(shí)現(xiàn)(3) - 使用配置AOP那些事 Spring validator 不說(shuō)其他的,先看代碼--> 第一步:定義一個(gè)Validator @Componentpublic class UserCUValidator implements Validator { private UserService userService; @Autowired public UserCUValidator(UserService userService) { this.userService=userService; } @Override public boolean supports(Class clazz) { return UserDomain.class.isAssignableFrom(clazz) || User.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { if (target instanceof UserDomain) { UserDomain domain=(UserDomain) target; if (StringUtils.isNullOrEmpty(domain.getUser().getName()) || StringUtils.isNullOrEmpty(domain.getUser().getPassword()) || StringUtils.isNullOrEmpty(domain.getRePwd())) { errors.rejectValue("user.field", "user.field.invalid", "user field is invalid"); return; } if (!domain.getUser().getPassword().equals(domain.getRePwd())) { errors.rejectValue("user.password.different", "user.password.different", "user password is different"); return; } } else if (target instanceof User) { ValidationUtils.rejectIfEmpty(errors, "name", "name.empty"); ValidationUtils.rejectIfEmpty(errors, "password", "password.empty"); } } } 第二步:使用Validator @RestController@RequestMapping("/users")public class UserController { @Inject private UserService userService; @Autowired private UserCUValidator userCUValidator; /** * 在Spring的數(shù)據(jù)綁定器中進(jìn)行注冊(cè),,而注冊(cè)時(shí)機(jī)是特定于控制器的 * * @param binder */ @InitBinder protected void initBinder(WebDataBinder binder) { binder.addValidators(userCUValidator); } @RequestMapping(value="/login", method=RequestMethod.POST) public ResponseEntity loginUser(@RequestBody @Valid User user, HttpServletRequest request) { ResponseEntity responseEntity=userService.loginUser(user); if (Boolean.parseBoolean(responseEntity.getFlag())) { user.setPassword(null); HttpSession session=request.getSession();//sample as request.getSession(true) session.setAttribute(CommonConstant.SESSION_CURRENT_USER, user); } return responseEntity; } 很簡(jiǎn)單,原理就是在映射到具體的方法后,,優(yōu)先validator校驗(yàn). 關(guān)于啟動(dòng)時(shí)注解 @SpringBootApplication@ComponentScan(basePackages="com.wyp.boot.earthlyfisher")public class BootEntry { /** * 1.If you need to find out what auto-configuration is currently being * applied, and why, start your application with the --debug switch . * * @param args */ /** * @param args */ public static void main(String[] args) { // SpringApplication.run(BootEntry.class, setBootArgs(args)); SpringApplication application=new SpringApplication(BootEntry.class); /* * Banner.Mode.OFF:關(guān)閉; Banner.Mode.CONSOLE:控制臺(tái)輸出,默認(rèn)方式; * Banner.Mode.LOG:日志輸出方式; */ application.setBannerMode(Banner.Mode.OFF); application.run(args); //application.run(setBootArgs(args)); } /** * command line args self define * * @param args * @return */ private static String[] setBootArgs(String[] args) { List argsLst=new ArrayList(); for (int i=0; i < args.length; i++) { argsLst.add(args[i]); } /** command line properties always take precedence over other property * sources,and self-add command param */ argsLst.add("--debug"); String[] springArgs=new String[argsLst.size()]; argsLst.toArray(springArgs); return springArgs; } } @SpringBootApplication same as @Configuration @EnableAutoConfiguration @ComponentScan provides aliases to customize the attributes of @EnableAutoConfiguration and @ComponentScan 關(guān)于日志 如果日志配置如下: logging: file: D:\\image\\earthlyfihser.log path: D:\\image level: root: info logging.file : 輸出到指定的文件,,可以為相對(duì)路徑或者絕對(duì)路徑logging.path : 與logging.file 是互斥的,,指定文件的路徑,默認(rèn)的文件名為 spring.log 日志文件默認(rèn)達(dá)到10Mb 時(shí),,將會(huì)從新打開(kāi)一個(gè)文件輸出,,默認(rèn)的日志輸出級(jí)別為ERROR,WARN 和 INFO,。需要注意的是日志系統(tǒng)的初始化要早于系統(tǒng)的生命周期,,因此logging properties 不能夠通過(guò)@PropertySource 注解獲取。 這就是基礎(chǔ)的日志配置,,可以直接在applicationperties配置,,我們還可以在classpath路徑下,通過(guò)定義具體的日志文件來(lái)配置. Spring Boot 采用 Commons Logging 作為內(nèi)部的日志框架,。 在默認(rèn)情況下,,采用 Starters 來(lái)啟動(dòng)Spring Boot 項(xiàng)目,Logback 是默認(rèn)的日志實(shí)現(xiàn)方案,。 多數(shù)據(jù)源配置: Spring Boot多數(shù)據(jù)源配置與使用 關(guān)于打成war 在一般的項(xiàng)目中,,需要將項(xiàng)目打包成war包供發(fā)布到tomcat,所以自己實(shí)現(xiàn)了下: extends SpringBootServletInitializer,重寫configure方法. @SpringBootApplication public class BootEntry4War extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(BootEntry4War.class); } /** * 1.If you need to find out what auto-configuration is currently being * applied, and why, start your application with the --debug switch . * * @param args */public static void main(String[] args) { SpringApplication application=new SpringApplication(BootEntry4War.class); application.run(args); } } 更改pom文件打包形式為war為了確保內(nèi)嵌的servlet容器不能干擾war包將部署的servlet容器。為了達(dá)到這個(gè)目的,,你需要將內(nèi)嵌容器的依賴標(biāo)記為provided,。 加載時(shí)預(yù)設(shè)環(huán)境變量值問(wèn)題 關(guān)于Environment的使用 通過(guò)實(shí)現(xiàn)EnvironmentAware可以獲取加載的環(huán)境變量,命令行參數(shù)以及在application文件預(yù)設(shè)值的變量等. public class BootEntry implements EnvironmentAware{ @Override public void setEnvironment(Environment environment) { System.out.println(environment.getProperty("spring.datasource.password")); } } 當(dāng)然以上已經(jīng)是完全加載后,并初始化所有Bean后,,此時(shí)已經(jīng)不能對(duì)加載的值中間修改操作,。 覆蓋加載的變量值的幾種方法 通過(guò)增加EnvironmentPostProcessor的實(shí)現(xiàn)來(lái)實(shí)現(xiàn)該功能 public class CustomeEnvPostProcessor implements EnvironmentPostProcessor {/** * ConfigurableEnvironment的異變PropertySource key. */private static final String SOURCE_NAME="applicationConfigurationProperties";/** * 需要的application變量的key */private static final String ENV_KEY_NAME="spring.datasource.password";@SuppressWarnings("unchecked")@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { MutablePropertySources ms=environment.getPropertySources(); if (ms.contains(SOURCE_NAME)) { /* * 1.獲取加載application文件的PropertySource */ PropertySource source=ms.get(SOURCE_NAME); if (source.containsProperty(ENV_KEY_NAME)) { /* * 2.由于復(fù)合關(guān)系, * 在這里找出加載application文件的PropertySource下的所有EnumerableCompositePropertySource */ List lst=(List) source .getSource(); /* * 3.遍歷EnumerableCompositePropertySource集合,, * 找出每一個(gè)PropertySource的子PropertySource集合,并替換需要的key-value */ for (EnumerableCompositePropertySource target : lst) { if (target.containsProperty(ENV_KEY_NAME)) { Collection<propertysource> sourceSet=target.getSource(); for (PropertySource propertySource : sourceSet) { if (propertySource instanceof MapPropertySource) { MapPropertySource targetSource=(MapPropertySource) propertySource; addOrReplaceProperty(targetSource); } } } } } } }/** * 在此做一些對(duì)application變量的替換 * * @param targetSource */private void addOrReplaceProperty(MapPropertySource targetSource) { targetSource.getSource().put(ENV_KEY_NAME, PasswordUtil.decodePassword(targetSource.getProperty(ENV_KEY_NAME) + "")); } } 通過(guò)追蹤源碼終于搞定了,,好辛苦. 對(duì)于上面的實(shí)現(xiàn),需要將其加到META-INF/spring-factories文件里,如下: #Environment Post Processor org.springframework.boot.env.EnvironmentPostProcessor=\ com.wyp.boot.earthlyfisher.system.EnvPropertiesHandler 通過(guò)增加PropertySourceLoader的實(shí)現(xiàn)來(lái)實(shí)現(xiàn)該功能 由于Springboot初始化加載文件是通過(guò)實(shí)現(xiàn)了PropertySourceLoader的class集來(lái)完成的,,所以可以通過(guò)這種方式來(lái)實(shí)現(xiàn).我是這么想的,,由于application文件后綴無(wú)非是yml或者properties,所以完全可以調(diào)用這兩種文件格式的加載方式來(lái)解決,需要做的只是對(duì)特定的key做擴(kuò)展. public class CustomPropertLoader implements PropertySourceLoader { @Override public String[] getFileExtensions() { return new String[] { "yml", "yaml" }; } @Override public PropertySource load(String name, Resource resource, String profile) throws IOException { YamlPropertySourceLoader ymlLoader=new YamlPropertySourceLoader(); PropertySource source=(MapPropertySource) ymlLoader.load(name, resource, profile); String propertyKey="spring.datasource.password"; if (source instanceof MapPropertySource) { MapPropertySource target=(MapPropertySource) source; if (target.containsProperty(propertyKey)) { String pwd=target.getProperty(propertyKey) + ""; target.getSource().put(propertyKey, pwd); } } return source; } } 當(dāng)然此種實(shí)現(xiàn),也需要將其加到META-INF/spring-factories文件里,如下: #PropertySourceLoaderorg.springframework.boot.envpertySourceLoader=\ com.wyp.boot.earthlyfisher.system.CustomPropertyHandler 重寫特定的使用實(shí)例 比如 spring.datasource.password是為了dataSource的使用而設(shè)置的,,完全可以自己定義數(shù)據(jù)源實(shí)例,,用來(lái)替代SpringBoot自動(dòng)配置為你生成的數(shù)據(jù)源。只不過(guò)這中方式比較復(fù)雜,得把需要的屬性重新配置一遍,,如下: @Configuration public class CustomDataSource { @Value(value="${spring.datasource.driverClass}") String driverClassName; @Value(value="${spring.datasource.url}") String url; @Value(value="${spring.datasource.username}") String username; @Value(value="${spring.datasource.password}") String password; @Bean @Primary public DataSource dataSource() { DataSource dataSource=new DataSource(); // org.apache.tomcat.jdbc.pool.DataSource; dataSource.setDriverClassName(driverClassName); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); return dataSource; } } 覆蓋PropertyPlaceholderConfigurer的實(shí)現(xiàn)此種方式是在普通的Spring項(xiàng)目中自己處理配置文件的一般方式,,但是由于springboot內(nèi)部處理的原因,暫時(shí)還沒(méi)有成功,,具體的錯(cuò)誤原因是,,只要我覆蓋了原生的實(shí)現(xiàn),則通過(guò)自定義注解配置屬性時(shí),,會(huì)找不到,,還需看源碼以進(jìn)一步研究.錯(cuò)誤信息如下: Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'spring.datasource.driver-class-name' in string value "${spring.datasource.driver-class-name}"at org.springframework.utilpertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174) ~[spring-core-4.3.4.RELEASE.jar:4.3.4.RELEASE] at org.springframework.utilpertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126) ~[spring-core-4.3.4.RELEASE.jar:4.3.4.RELEASE] 以上四種方式以第一種方式為佳,因?yàn)槠洳粻砍兜秸5募虞d邏輯,算是在所有參數(shù)加載完后做的一些補(bǔ)充. Docker化部署 編寫Dockerfile FROM frolvlad/alpine-oraclejdk8:slim VOLUME /tmp ADD earthlyfisher.jar app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] 首先需要配置pom文件中Docker相關(guān)插件 maven-compiler-plugin 1.7 1.7 org.springframework.boot spring-boot-maven-plugin org.springframework springloaded 1.2.4.RELEASE repackage com.spotify docker-maven-plugin 0.4.13 mytest/springboottest src/main/docker / ${project.build.directory} ${project.build.finalName}.jar earthlyfisher |
|
來(lái)自: 新用戶0175WbuX > 《待分類》