【走近Spring】控制Spring IoC容器對Bean(含@Configuration配置類)的加載順序(@DependsOn注解的使用)


為什么要控制Bean的加載順序?

@Order注解等並不能控制Bean的加載順序的~~因為Spring在解析Bean的時候,根本就沒有參考這個注解。另外@Configuration配置類的加載,也不會受到@Order注解的影響,它拿到配置的數組后,僅僅就是一個for循環遍歷去解析。

另外需要說明的一點是:@Configuration注解的解析順序,在Spring Boot環境下會受到影響的(畢竟Boot都是自動的,而不是我們手動傳值的) 相關注解有:@AutoConfigureAfter、@AutoConfigureBefore、@AutoconfigureOrder等等

Spring容器載入bean順序是不確定的,Spring框架沒有約定特定順序邏輯規范。

但Spring能保證如果A依賴B(如beanA中有@Autowired B的變量),那么B將先於A被加載(這屬於Spring容器內部就自動識別處理了)。但如果beanA不直接依賴B,我們如何讓B仍先加載?

需要的場景如下:

  • bean A 間接(並不是直接@Autowired)依賴 bean B。如bean A有一個屬性,需要在初始化的時候對其進行賦值(需要在初始化的時候做,是因為這個屬性其實是包裝了其它的幾個Bean的,比如說代理了Bean B),所以這就形成了Bean A間接的依賴Bean B了
  • bean A是事件發布者(或JMS發布者),bean B (或一些) 負責監聽這些事件,典型的如觀察者模式。我們不想B 錯過任何事件,那么B需要首先被初始化。

以上是兩種典型的,Bean初始化的時候存在依賴關系的情況,都可以通過@DependsOn來解決。(當然有的時候可以通過別的方式間接解決,比如特殊接口SmartInitializingSingleton ,又或者是Spring Boot提供的CommandLineRunner、ApplicationRunner等接口,但這些都不是本文研究的重點)。

Bean加載順序、依賴關系示例

這里面解答的方案,不考慮上面說到的使用SmartInitializingSingleton等間接的方案

准備工作:(兩個controller和一個service)

@Controller
public class HelloController {
	public HelloController() {
        System.out.println("HelloController 初始化。。。");
    }
    @ResponseBody
    @GetMapping("/hello")
    public String helloGet() throws Exception {
        return "hello...Get";
    }
}

@Controller
public class AsyncHelloController {
	public AsyncHelloController() {
        System.out.println("AsyncHelloController 初始化。。。");
    }
}

@Service
public class HelloServiceImpl implements HelloService {
    public HelloServiceImpl() {
        System.out.println("HelloServiceImpl 初始化。。。");
    }	
}

啟動容器,打印順序(初始化順序如下:)

HelloServiceImpl 初始化。。。
AsyncHelloController 初始化。。。
HelloController 初始化。。。

需要注意的是:這個demo的日志都是放在默認的構造函數里面的,因此即使你使用了@Autowired,也是不會打亂構造函數的執行順序的,因為@Autowired的解析發生在給屬性賦值的populate()方法里(具體查之前博文或者源碼),這個時候自己已經實例化了,才會去給屬性賦值。所以如果你要求的時機稍微比較晚可以在賦值期間、或者實例化期間去。

@DependsOn:讓HelloController在AsyncHelloController之前實例化

//@DependsOn // 這里面寫String數組。不寫不會生效,但是若寫了,名字要寫正確,否則會報錯的
@DependsOn({"helloController"}) // 名稱必須寫對,必須是容器里存在的Bean,否則啟動報錯的(fast-fail是好事)
@Controller
public class AsyncHelloController {
	...
}

HelloServiceImpl 初始化。。。
HelloController 初始化。。。   --> HelloController先被實例化了~~~
AsyncHelloController 初始化。。。

需要特別注意的是,使用@DependsOn注解時,一定要注意父子容器的問題(因為它底層也是getBean())。比如下面這樣在service層依賴controller的話,就報錯:

@DependsOn({"helloController"}) //NoSuchBeanDefinitionException: No bean named 'helloController' available
@Service
public class HelloServiceImpl implements HelloService {
	...
}

SpringBoot環境下,不會報錯。

使用@Lazy間接實現

@Lazy
public class AsyncHelloController {
	...
}

HelloServiceImpl 初始化。。。
HelloController 初始化。。。

我們發現它只有兩句輸出,這個時候AsyncHelloController還沒有實例化。只有首次訪問它的時候才會實例化,所以我們是通過間接的方式實現了這個效果。

這種方式不建議使用在這種DependsOn的場景,因為它不是為了這個而生的。若有別的Bean @Autowired了它之類的,這種做法顯然就失效了。

//May be used on any class directly or indirectly annotated with @Component
// or on methods annotated with @Bean
// 若這個Bean xml里也配置了,就會議xml里配置的為准
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DependsOn {
	String[] value() default {};
}

@DependsOn 用於@Bean注解上的使用

@Configuration配置類順序控制

@Configuration配置類也是容器里面一個特殊的Bean,因為它不需要完成業務功能。

純Spring環境

由於在純Spring環境下,Config配置類都是由我們手動指定傳進去的,所以Spring並沒有再對它進行排序處理。如下非web環境和web環境:

public static void main(String[] args) {
	// 這里Config是自己指定、所以加載順序就是我們傳入的順序
	new AnnotationConfigApplicationContext(RootConfig.class, Root2Config.class);
}

@Override
protected Class<?>[] getRootConfigClasses() {
	return new Class<?>[]{RootConfig.class, Root2Config.class};
}

@Configuration的加載順序,並不影響@Bean的互相引用:

@Configuration
public class RootConfig {
	// 雖然入參里的Parent 在配置類Root2Config里,但spring還是能夠去容器中找過來的。
    @Bean
    public Child child(Parent parent) {
        System.out.println(parent); 
        return new Child();
    }
}

@Configuration
public class Root2Config {
    @Bean
    public Parent parent() {
        return new Parent();
    }
}

配置文件加載順序為:RootConfig 、 Root2Config
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class, Root2Config.class};
    }

我們可以看到,Config的先后順序,並不影響@Bean的引用。

此處需要特別說明的一點是:請不要循環引用,否則會報錯~(這個和Bean的屬性賦值方面的循環引用還是不一樣的,有點類似構造器的循環引用。我們知道的是,Spring是不能解決構造器的循環引用的)

 

參考:

【小家Spring】控制Spring IoC容器對Bean(含@Configuration配置類)的加載順序(@DependsOn注解的使用)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM