為什么要控制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注解的使用) |
