假設A,B兩個 bean 都需要在初始化的時候從本地磁盤讀取文件,其中B加載的文件,依賴A中加載的全局配置文件中配置的路徑,所以需要A先於B初始化,此外A中的配置改變后也需要觸發B的重新加載邏輯,所以A,B需要注入彼此。
1. 業務中判斷和控制bean初始化順序
我們可以在業務層自己控制A,B的初始化順序,在A中設置一個“是否初始化的”標記,B初始化前檢測A是否得以初始化,如果沒有則調用A的初始化方法,所謂的check-and-act。
2. 使用DependsOn注解
Spring 中的 DependsOn 注解可以保證被依賴的bean先於當前bean被容器創建,對於上述模型,如果在B上加上注解@DependsOn({"a"}),邏輯如下:
先加載的bean A,最終通過無參構造器構造,然后,繼續屬性填充(populateBean),發現需要注入 bean B。所以轉而加載 bean B(遞歸調用 getBean())。此時發現 bean B 需要 DependsOn("a"),在保存依賴關系(為了防止循環 depends)后,調用 getBean("a"),此時會得到提前暴露的 bean A ,所以繼續 B 的加載,流程為: 初始化策略構造實例 -> 屬性填充(同樣會注入提前暴露的 bean A ) -> 調用初始化方法。
DependsOn只是保證的被依賴的bean先於當前bean被實例化,被創建,所以如果要采用這種方式實現bean初始化順序的控制,那么可以把初始化邏輯放在構造函數中,但是復雜耗時的邏輯仿造構造器中是不合適的,會影響系統啟動速度。
在這里問題的關鍵是:bean屬性的注入是在初始化方法調用之前。
// 代碼位置:AbstractAutowireCapableBeanFactory.doCreateBean
// 填充 bean 的各個屬性,包括依賴注入
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
// 調用初始化方法,如果是 InitializingBean 則先調用 afterPropertiesSet 然后調用自定義的init-method 方法
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
然后通過三級緩存來解決:
參考博文:spring 如何利用三級緩存解決循環依賴
3. 使用Spring框架擴展點
spring有很多擴展點為用戶開放,什么時候可以控制初始化順序呢,在容器加載bean之前,其中 BeanFactoryPostProcessor 可以允許我們在容器加載任何bean之前修改應用上下文中的BeanDefinition,在本例中,就可以把A的初始化邏輯放在一個BeanFactoryPostProcessor 中。
@Component
public class ABeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
A.initA();
}
這種方式把A中的初始化邏輯放到了加載bean之前,很適合加載系統全局配置,但是這種方式中初始化邏輯不能依賴bean的狀態
4. 事件監聽器的有序性
Spring 中的 Ordered 也是一個很重要的組件,很多邏輯中都會判斷對象是否實現了 Ordered 接口,如果實現了就會先進行排序操作。比如在事件發布的時候,對獲取到的 ApplicationListener 會先進行排序。
所以可以利用事件監聽器在處理事件時的有序性,在應用上下文 refresh 完成后,分別實現A,B中對應的初始化邏輯。
這種方式就是站在事件響應的角度,上下文加載完成后,先實現A邏輯,然后實現B邏輯。
參考資料:
https://blog.csdn.net/u012045045/article/details/86100670