Java中如何控制bean的加載順序
寫在前面
springboot
遵從約定大于配置的原則,極大程度的解決了配置繁瑣的問題。在此基礎(chǔ)上,又提供了spi機制,用spring.factories
可以完成一個小組件的自動裝配功能。
在一般業(yè)務(wù)場景,可能是不需要關(guān)心一個bean是如何被注冊進spring容器的,只需要把需要注冊進容器的bean聲明為@Component
即可,因為spring會自動掃描到這個Bean完成初始化并加載到spring上下文容器。
但是,如果加載Bean的過程中部分Bean和Bean之間存在依賴關(guān)系,也就是說Bean A
的加載需要等待Bean B
加載完成之后才能進行;或者你正在開發(fā)某個中間件需要完成自動裝配時,你會聲明自己的Configuration類,但是可能你面對的是好幾個有互相依賴的Bean,如果不加以控制,這時候可能會報找不到依賴的錯誤。
而Spring框架在沒有明確指定加載順序的情況下是無法按照業(yè)務(wù)邏輯預期的順序進行Bean加載,所以需要Spring框架提供能讓開發(fā)人員顯示地指定Bean加載順序的能力。
幾個誤區(qū)
在正式說如何控制加載順序之前,先說2個誤區(qū):
- 在標注了
@Configuration
的類中,寫在前面的@Bean一定會被先注冊嗎?
這個不存在的,spring在xml的時代,也不存在寫在前面一定會被先加載的邏輯。因為xml不是漸進的加載,而是全部parse好,再進行依賴分析和注冊。到了springboot中,只是省去了xml被parse成spring內(nèi)部對象的這一過程,但是加載方式并沒有大的改變。
- 利用
@Order
這個標注就一定能進行加載順序的控制嗎?
嚴格的說,不是所有的Bean都可以通過@Order
這個標注進行順序的控制。因為把@Order
這個標注加在普通的方法上或者類上是沒有影響的,
那@Order
能控制哪些bean的加載順序呢?官方解釋:
java
{@code @Order} defines the sort order for an annotated component. Since Spring 4.0, annotation-based ordering is supported for many kinds of components in Spring, even for collection injection where the order values of the target components are taken into account (either from their target class or from their {@code @Bean} method). While such order values may influence priorities at injection points, please be aware that they do not influence singleton startup order which is an orthogonal concern determined by dependency relationships and {@code @DependsOn} declarations (influencing a runtime-determined dependency graph).
最開始@Order
注解用于切面的優(yōu)先級指定;在 4.0 之后對它的功能進行了增強,支持集合的注入時,指定集合中 bean 的順序,并且特別指出了,它對于單實例的 bean 之間的順序,沒有任何影響。目前用的比較多的有以下3點:
- 控制AOP的類的加載順序,也就是被
@Aspect
標注的類 - 控制
ApplicationListener
實現(xiàn)類的加載順序 - 控制
CommandLineRunner
實現(xiàn)類的加載順序
使用詳情請看后文
如何控制
@Conditional 條件注解家族
- @ConditionalOnClass:當類路徑下存在指定的類時,配置類才會生效。
@Configuration // 當類路徑下存在指定的類時,配置類才會生效。 @ConditionalOnClass(name = "com.example.SomeClass") public class MyConfiguration { // ... }
- @ConditionalOnMissingClass:當類路徑下不存在指定的類時,配置類才會生效。
- @ConditionalOnBean:當容器中存在指定的Bean時,配置類才會生效。
- @ConditionalOnMissingBean:當容器中不存在指定的Bean時,配置類才會生效。
@DependsOn
@DependsOn
注解可以用來控制bean的創(chuàng)建順序,該注解用于聲明當前bean依賴于另外一個bean。所依賴的bean會被容器確保在當前bean實例化之前被實例化。
@DependsOn
的使用:
- 直接或者間接標注在帶有
@Component
注解的類上面; - 直接或者間接標注在帶有
@Bean
注解的方法上面; - 使用
@DependsOn
注解到類層面僅僅在使用 component-scanning 方式時才有效,如果帶有@DependsOn
注解的類通過XML方式使用,該注解會被忽略,<bean depends-on="..."/>
這種方式會生效。
示例:
@Configuration public class BeanOrderConfiguration { @Bean @DependsOn("beanB") public BeanA beanA(){ System.out.println("bean A init"); return new BeanA(); } @Bean public BeanB beanB(){ System.out.println("bean B init"); return new BeanB(); } @Bean @DependsOn({"beanD","beanE"}) public BeanC beanC(){ System.out.println("bean C init"); return new BeanC(); } @Bean @DependsOn("beanE") public BeanD beanD(){ System.out.println("bean D init"); return new BeanD(); } @Bean public BeanE beanE(){ System.out.println("bean E init"); return new BeanE(); } }
以上代碼bean的加載順序為:
bean B init bean A init bean E init bean D init bean C init
參數(shù)注入
在@Bean
標注的方法上,如果傳入了參數(shù),springboot會自動會為這個參數(shù)在spring上下文里尋找這個類型的引用。并先初始化這個類的實例。
利用此特性,我們也可以控制bean的加載順序。
示例:
@Bean public BeanA beanA(BeanB demoB){ System.out.println("bean A init"); return new BeanA(); } @Bean public BeanB beanB(){ System.out.println("bean B init"); return new BeanB(); }
以上結(jié)果,beanB先于beanA被初始化加載。
需要注意的是,springboot會按類型去尋找。如果這個類型有多個實例被注冊到spring上下文,那就需要加上@Qualifier("Bean的名稱")
來指定
利用bean的生命周期中的擴展點
在spring體系中,從容器到Bean實例化&初始化都是有生命周期的,并且提供了很多的擴展點,允許在這些步驟時進行邏輯的擴展。
這些可擴展點的加載順序由spring自己控制,大多數(shù)是無法進行干預的??梢岳眠@一點,擴展spring的擴展點。在相應的擴展點加入自己的業(yè)務(wù)初始化代碼。從來達到順序的控制。
具體關(guān)于spring容器中大部分的可擴展點的分析,之前已經(jīng)寫了一篇文章詳細介紹了:Spring&SpringBoot中所有的擴展點
實現(xiàn)Ordered/PriorityOrdered接口/注解
在Spring中提供了如下的方法來進行Bean加載順序的控制:
- 實現(xiàn)
Ordered/PriorityOrdered
接口,重寫order方法 - 使用
@Order/@Priority
注解,@Order
注解可以用于方法級別,而@Priority
注解則不行;
針對自定義的Bean而言,上述的方式都可以實現(xiàn)Bean加載順序的控制。無論是實現(xiàn)接口的方式還是使用注解的方式,值設(shè)置的越小則優(yōu)先級越高,而通過實現(xiàn)PriorityOrdered接口或者使用@Priority注解的Bean時其加載優(yōu)先級會高于實現(xiàn)Ordered接口或者使用@Order注解的Bean。
需要注意的是,使用上述方式只會改變實現(xiàn)同一接口Bean加載到集合(比如List、Set等)中的順序(或者說優(yōu)先級),但是這種方式并不會影響到Spring應用上下文啟動時不同Bean的初始化順序(startup order)。
- 錯誤案例:以下案例代碼是無法指定配置順序的
@Component @Order(1) public class BeanA { // BeanA的定義 } @Component @Order(2) public class BeanB { // BeanB的定義 }
- 正確使用案例:
首先定義兩個 Bean 實現(xiàn)同一個接口,并添加上@Order注解。
public interface IBean { } @Order(2) @Component public class AnoBean1 implements IBean { private String name = "ano order bean 1"; public AnoBean1() { System.out.println(name); } } @Order(1) @Component public class AnoBean2 implements IBean { private String name = "ano order bean 2"; public AnoBean2() { System.out.println(name); } }
然后在一個測試 bean 中,注入IBean
的列表,我們需要測試這個列表中的 Bean 的順序是否和定義的@Order
規(guī)則一致
@Component public class AnoTestBean { public AnoTestBean(List<IBean> anoBeanList) { for (IBean bean : anoBeanList) { System.out.println("in ano testBean: " + bean.getClass().getName()); } } }
@AutoConfigureOrder
這個注解用來指定配置文件的加載順序。但是在實際測試中發(fā)現(xiàn),以下這樣使用是不生效的:
@Configuration @AutoConfigureOrder(2) public class BeanOrderConfiguration1 { @Bean public BeanA beanA(){ System.out.println("bean A init"); return new BeanA(); } } @Configuration @AutoConfigureOrder(1) public class BeanOrderConfiguration2 { @Bean public BeanB beanB(){ System.out.println("bean B init"); return new BeanB(); } }
無論你2個數(shù)字填多少,都不會改變其加載順序結(jié)果。那這個@AutoConfigureOrder
到底是如何使用的呢?
@AutoConfigureOrder適用于外部依賴的包中 AutoConfig 的順序,而不能用來指定本包內(nèi)的順序。能被你工程內(nèi)部scan到的包,都是內(nèi)部的Configuration,而spring引入外部的Configuration,都是通過spring特有的spi文件:spring.factories
換句話說,@AutoConfigureOrder
能改變spring.factories
中的@Configuration
的順序。
具體使用方式:
@Configuration @AutoConfigureOrder(10) public class BeanOrderConfiguration1 { @Bean public BeanA beanA(){ System.out.println("bean A init"); return new BeanA(); } } @Configuration @AutoConfigureOrder(1) public class BeanOrderConfiguration2 { @Bean public BeanB beanB(){ System.out.println("bean B init"); return new BeanB(); } }
spring.factories
:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.demo.BeanOrderConfiguration1,\ com.example.demo.BeanOrderConfiguration2
總結(jié)
其實在工作中,我相信很多人碰到過復雜的依賴關(guān)系的bean加載,把這種不確定性交給spring去做,還不如我們自己去控制,這樣在閱讀代碼的時候 ,也能輕易看出bean之間的依賴先后順序。
到此這篇關(guān)于Java中如何控制bean的加載順序的文章就介紹到這了,更多相關(guān)java bean加載順序內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
jdk-logging?log4j?logback日志系統(tǒng)實現(xiàn)機制原理介紹
這篇文章主要介紹了jdk-logging、log4j、logback日志介紹以及三個日志系統(tǒng)的實現(xiàn)機制,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-03-03Java監(jiān)聽器ActionListener與MouseListener的執(zhí)行順序說明
這篇文章主要介紹了Java監(jiān)聽器ActionListener與MouseListener的執(zhí)行順序說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12eclipse創(chuàng)建springboot項目的三種方式總結(jié)
這篇文章主要介紹了eclipse創(chuàng)建springboot項目的三種方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07SpringBoot統(tǒng)一處理功能實現(xiàn)的全過程
最近在做項目時需要對異常進行全局統(tǒng)一處理,主要是一些分類入庫以及記錄日志等,下面這篇文章主要給大家介紹了關(guān)于SpringBoot統(tǒng)一功能處理實現(xiàn)的相關(guān)資料,文中通過圖文以及實例代碼介紹的非常詳細,需要的朋友可以參考下2023-03-03SpringBoot集成geodesy實現(xiàn)距離計算功能
Geodesy:大地測量學的神奇力量 Geodesy,又稱大地測量學,是一門研究地球形狀、大小及其重力場的學科,在地球距離計算中,它扮演著至關(guān)重要的角色,故本文給大家介紹了SpringBoot集成geodesy實現(xiàn)距離計算功能,感興趣的朋友可以參考下2024-06-06