在spring中我們有可能會遇到這種情況,A依賴B,B又依賴A,正常情況下,我們用@Reference或者@Autowired註解,是不會有問題的,可在我們用構造方法的時候,就會出現問題:
public AssistantDoctorController(UserDoctorController userDoctorController) {
this.userDoctorController = userDoctorController;
}
public UserDoctorController(AssistantDoctorController assistantDoctorController) {
this.assistantDoctorController = assistantDoctorController;
}
報錯的意思是被請求的bean正在被創建,是否存在循環依賴,那spring是如何判斷循環依賴,又是如何解決循環依賴的呢,網絡上很多文章說的有些不是很清楚,所以想寫一篇博文,說的儘量清晰些。
一、爲什麼構造方法的循環依賴會報錯
首先看下AbstractBeanFactoryBeanFactory的結構圖:
可以發現AbstrapctBeanFactory繼承了DefaultSingletonBeanRegistry類,這個類是註冊bean使用的,可見註冊bean也是在AbstractBeanFactory中完成,在註冊bean之前會調用它的beforeSingletonCreation方法,如下:
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
這個方法中會檢查singletonsCurrentlyInCreation屬性是否存在這個bean,沒有就放入,有則會拋出異常,等到創建bean完成,再調用afterSingletonCreation方法將已經創建的bean刪除。
- singletonsCurrentlyInCreation:記錄正在創建中的bean。
我舉例說明:
A->B->A
- 在創建A的時候,A的beanName會被加入到singletonsCurrentlyInCreation中
- 因爲A依賴了B,此時循環創建B,然後將B放入到singletonsCurrentlyInCreation中,
- 此時B又依賴了A,然後再將A放入到singletonsCurrentlyInCreation中,此時因爲有A在singletonsCurrentlyInCreation中,所以放入失敗,就會拋出異常。
爲什麼用註解循環依賴不會報錯
因爲在創建bean的時候,會調用bean的構造方法去實例化bean,並緩存到beanFactory中,而用註解做的構造方法中,不需要在構造方法中注入bean,所以不會報錯。
總結就是:構造方法中的bean是在創建bean的時候注入的,而註解的bean是後面注入的,所以報錯是因爲構造方法的緣故。
那麼問題又來了,註解注入的bean是在什麼時間注入的呢?
註解注入Bean的時機
真正的執行注入,是在當前bean的依賴bean創建完成之後的,AbstractAutowireCapableBeanFactory類中的doCreateBean方法中的populateBean方法中完成的。
代碼如下:
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvs == null) {
return;
}
}
}
其中注入@Resource註解,使用的是CommonAnnotationBeanPostProcessor,注入@AutoWired註解使用的是AutowiredAnnotationBeanPostProcessor,執行順序是限制性CommonAnnotationBeanPostProcessor,再執行AutowiredAnnotationBeanPostProcessor,所以注入的時候會先根據名稱注入,然後根據類型進行注入。
循環依賴的解決
那麼注入了Bean之前,如果有循環依賴如何解決呢,我們要先明白幾個屬性,幾個DefaultSingletonBeanRegistry的屬性,而我認爲理解循環依賴的關鍵就是弄懂這幾個屬性,以及他們何時進行了操作
- singletonFactories:bean的創建方法的緩存。
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
這個值是什麼時候設置進去的呢?是在doCreateBean方法中,創建了BeanInstance之後,代碼如下:
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
這個ObjectFactory的getObject()中有三個入參:
- beanName
- mbd:作用不大
- bean:此時創建的bean是通過createBeanInstance方法創建的,還沒有進行populateBean方法,所以現在的an是沒有任何屬性注入的,用構造方法創建出來的一個Bean,而getObject返回的就是這樣的一個早期的Bean,所以叫EarlyBean。
- 這個beanName對應的ObjecFactory會在EarlyBean添加到earlySingletonObjects屬性中後進行刪除,代碼如下:
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
- registeredSingletons:DefaultSingletonBeanRegistry的這個屬性是按照依賴的順序放入了beanName,在spring的使用中無用,主要是給springMoce或者其他方面,可以不用關注。
- earlySingletonObjects:這個屬性的key爲beanName,value是我們上面說的earlyBean。bean放入到earlySingletonObjects的時機是在設置注入屬性的時候,就是在:
- CommonAnnotationBeanPostProcessor設置@Resource屬性時候或者
- AutowiredAnnotationBeanPostProcessor設置@Autowired屬性的時候
通過beanName調用DefaultSingletonBeanRegistry的getSingleton方法的時候進行的插入,以及singletonFactories中屬性的刪除
- singletonObjects:這個屬性中緩存了beanName和對應的已經注入好的Bean
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
這個裏面的Bean會在Bean中的注入屬性已經完成注入,成爲一個完整正常的bean之後放入的。它是在AbstractBeanFactory的doGetBean方法中的DefaultSingletonBeanRegistry的addSingleton方法中完成的。
也有人把Bean的循環依賴注入成爲三級緩存完成那麼
- 第一層緩存:singletonFactories
- 第二層緩存:earlySingletonObjects
- 第三層緩存:singletonObjects
總結:這就是Bean的解決循環依賴的方法與過程,正常的Bean,即使沒有循環依賴也是這個處理邏輯