如何正確控制springboot中bean的載入順序總結

鉑賽東發表於2020-07-13

1.jpg

1.為什麼需要控制載入順序

springboot遵從約定大於配置的原則,極大程度的解決了配置繁瑣的問題。在此基礎上,又提供了spi機制,用spring.factories可以完成一個小元件的自動裝配功能。

在一般業務場景,可能你不大關心一個bean是如何被註冊進spring容器的。只需要把需要註冊進容器的bean宣告為@Component即可,spring會自動掃描到這個Bean完成初始化並載入到spring上下文容器。

而當你在專案啟動時需要提前做一個業務的初始化工作時,或者你正在開發某個中介軟體需要完成自動裝配時。你會宣告自己的Configuration類,但是可能你面對的是好幾個有互相依賴的Bean。如果不加以控制,這時候可能會報找不到依賴的錯誤。

但是你明明已經把相關的Bean都註冊進spring上下文了呀。這時候你需要通過一些手段來控制springboot中的bean載入順序。

2.幾個誤區

在正式說如何控制載入順序之前,先說2個誤區。

在標註了@Configuration的類中,寫在前面的@Bean一定會被先註冊

這個不存在的,spring在以前xml的時代,也不存在寫在前面一定會被先載入的邏輯。因為xml不是漸進的載入,而是全部parse好,再進行依賴分析和註冊。到了springboot中,只是省去了xml被parse成spring內部物件的這一過程,但是載入方式並沒有大的改變。

利用@Order這個標註能進行載入順序的控制

嚴格的說,不是所有的Bean都可以通過@Order這個標註進行順序的控制。你把@Order這個標註加在普通的方法上或者類上一點鳥用都沒有。

@Order能控制哪些bean的載入順序呢,我們先看看官方的解釋:

{@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註解用於切面的優先順序指定;在 4.0 之後對它的功能進行了增強,支援集合的注入時,指定集合中 bean 的順序,並且特別指出了,它對於但例項的 bean 之間的順序,沒有任何影響。

目前用的比較多的有以下3點:

  • 控制AOP的類的載入順序,也就是被@Aspect標註的類
  • 控制ApplicationListener實現類的載入順序
  • 控制CommandLineRunner實現類的載入順序

3.如何控制

3.1@DependsOn

@DependsOn註解可以用來控制bean的建立順序,該註解用於宣告當前bean依賴於另外一個bean。所依賴的bean會被容器確保在當前bean例項化之前被例項化。

示例:

@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

@DependsOn的使用:

  • 直接或者間接標註在帶有@Component註解的類上面;
  • 直接或者間接標註在帶有@Bean註解的方法上面;
  • 使用@DependsOn註解到類層面僅僅在使用 component-scanning 方式時才有效,如果帶有@DependsOn註解的類通過XML方式使用,該註解會被忽略,<bean depends-on="..."/>這種方式會生效。

3.2 引數注入

@Bean標註的方法上,如果你傳入了引數,springboot會自動會為這個引數在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();
}

以上結果,beanB先於beanA被初始化載入。

需要注意的是,springboot會按型別去尋找。如果這個型別有多個例項被註冊到spring上下文,那你就需要加上@Qualifier("Bean的名稱")來指定

3.3 利用bean的生命週期中的擴充套件點

在spring體系中,從容器到Bean例項化&初始化都是有生命週期的,並且提供了很多的擴充套件點,允許你在這些步驟時進行邏輯的擴充套件。

這些可擴充套件點的載入順序由spring自己控制,大多數是無法進行干預的。我們可以利用這一點,擴充套件spring的擴充套件點。在相應的擴充套件點加入自己的業務初始化程式碼。從來達到順序的控制。

具體關於spring容器中大部分的可擴充套件點的分析,之前已經寫了一篇文章詳細介紹了:《Springboot啟動擴充套件點超詳細總結,再也不怕面試官問了》。

3.4 @AutoConfigureOrder

這個註解用來指定配置檔案的載入順序。但是在實際測試中發現,以下這樣使用是不生效的:

@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個數字填多少,都不會改變其載入順序結果。

那這個@AutoConfigureOrder到底是如何使用的呢。

經過測試發現,@AutoConfigureOrder只能改變外部依賴的@Configuration的順序。如何理解是外部依賴呢。

能被你工程內部scan到的包,都是內部的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

4.總結

其實在工作中,我相信很多人碰到過複雜的依賴關係的bean載入,把這種不確定性交給spring去做,還不如我們自己去控制,這樣在閱讀程式碼的時候 ,也能輕易看出bean之間的依賴先後順序。

5.聯絡作者

微信關注 jishuyuanren 獲取更多技術乾貨

相關文章