前面的文章一直在研究Spring建立Bean的整個過程,建立一個bean是一個很是複雜的過程,而其中最難以理解的就是對循環依賴的處理,本文就來研究一下spring是如何處理循環依賴的。spring
無論以前是否研究過循環依賴,這裏先對這個知識作一點回顧。緩存
循環依賴就是循環引用,就是兩個或者多個bean相互之間的持有對方,好比A引用B,B引用C,C引用A,則它們最終反映爲一個環,參考下圖:函數
瞭解了什麼是循環依賴以後,咱們知道這是一種不可避免會出現的狀況,那做爲Bean容器的Spring又是怎麼處理這一問題呢?咱們接着往下看。測試
Spring容器循環依賴包括構造器循環依賴和setter循環依賴,那Spring容器又是如何解決循環依賴的呢?咱們來測試一下,首先咱們來定義循環引用類:ui
public class TestA{ private TestB testB; public void a(){ testB.b(); } public TestB getTestB(){ return testB; } public void setTestB(TestB testB){ this.testB = testB; } } public class TestB{ private TestC testC; public void b(){ testC.c(); } public TestC getTestC(){ return testC; } public void setTestC(TestC testC){ this.testC = testC; } } public class TestC{ private TestA testA; public void c(){ testA.a(); } public TestA getTestA(){ return testA; } public void setTestA(TestA testA){ this.testA = testA; } }
在Spring中將循環依賴的處理分紅了3種狀況:this
這表示經過構造器注入構成的循環依賴,此依賴是沒法解決的,只能拋出BeanCurrentlyInCreationException異常表示循環依賴。編碼
好比在建立TestA類時,構造器須要TestB類,那麼將去建立TestB,在建立TestB類時又發現須要TestC類,則又去建立TestC,最終在建立TestC時發現又須要TestA,從而造成一個環,沒辦法建立。spa
Spring容器將每個正在建立的bean標識符放在一個「當前建立bean池」中,bean標識符在建立過程當中將一直保持在這個池中,所以若是在建立bean的過程當中發現本身已經在「當前建立bean池」裏時,則拋出BeanCurrentlyInCreationException異常表示出現了循環依賴;而對於建立完畢的bean將從「當前建立bean池」中清除掉,這個「當前建立bean池」其實是一個ConcurrentHashMap,即DefaultSingletonBeanRegistry中的singletonsCurrentlyInCreation。prototype
咱們經過一個直觀的測試用例來進行分析:debug
xml配置以下:
<bean id = "testA" class = "xxx.xxx"> <constructor-arg index = "0" ref = "testB"/> </bean> <bean id = "testB" class = "xxx.xxx"> <constructor-arg index = "0" ref = "testC"/> </bean> <bean id = "testC" class = "xxx.xxx"> <constructor-arg index = "0" ref = "testA"/> </bean>
建立測試用例:
public static void main(String[] args) { try{ new ClassPathXmlApplicationContext("beans.xml"); }catch (Exception e){ e.printStackTrace(); } }
這個執行過程當中會拋出異常BeanCurrentlyInCreationException,經過debug能夠快速找到異常拋出的位置在getSingleton()方法中的beforeSingletonCreation():
protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.containsKey(beanName) && this.singletonsCurrentlyInCreation.put(beanName, Boolean.TRUE) != null) { throw new BeanCurrentlyInCreationException(beanName); } }
由此可知,Spring在對構造器循環依賴的處理策略上是選擇了直接拋異常,並且對循環依賴的判斷是發生在加載單例時調用ObjectFactory的getObject()方法實例化bean以前。
這個表示經過setter注入方式構成的循環依賴。對於setter注入形成的循環依賴Spring是經過提早暴露剛完成構造器注入但還未完成其餘步驟(如setter注入)的bean來完成的,並且只能解決單例做用域的bean循環代碼,咱們這裏來詳細分析一下Spring是如何處理的。
關於這部分的處理邏輯,在AbstractAutowireCapableBeanFactory的doCreateBean()方法中有一段代碼,以下所示:
// Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isDebugEnabled()) { logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } // 爲避免後期循環依賴,能夠在bean初始化完成前將建立實例的ObjectFactory加入工廠 addSingletonFactory(beanName, new ObjectFactory<Object>() { public Object getObject() throws BeansException { // 對bean再一次依賴引用,主要應用SmartInstantiationAwareBeanPostProcessor, // 其中咱們熟知的AOP就是在這裏將advice動態織入bean中,若沒有則直接返回bean,不作任何處理 return getEarlyBeanReference(beanName, mbd, bean); } }); }
這段代碼不是很複雜,可是若是是一開始看這段代碼的時候不太容易理解其做用,由於僅僅從函數中去理解是很難弄懂其中的含義,這裏須要從全局的角度去思考Spring的依賴解決辦法才能更好理解。
ClassPathXmlApplicationContext bf = new ClassPathXmlApplicationContext("aspectTest.xml"); bf.setAllowBeanDefinitionOverriding(false);
通過上面的分析能夠知道變量earlySingletonExposure爲是不是單例、是否容許循環依賴、是否對應的bean正在建立這三個條件的綜合。當這3個條件都知足時會執行addSingletonFactory操做,那麼加入SingletonFactory的做用又是什麼呢?
這裏仍是用一個最簡單的AB循環依賴爲例,類A中含有屬性類B,而類B中又會含有屬性類A,那麼初始化beanA的過程以下圖所示:
上圖展現了建立beanA的流程,圖中咱們看到,在建立A的時候首先會記錄類A所對應的beanName,並將beanA的建立工廠加入緩存中,而在對A的屬性填充也就是調用populate()方法的時候又會再一次的對B進行遞歸建立。一樣的,由於在B中一樣存在A屬性,所以在實例化B時的populate()方法中又會再次地初始化A,也就是圖形的最後,調用getBean(A)。關鍵就是在這裏,在這個getBean()函數中並非直接去實例化A,而是先去檢測緩存中是否有已經建立好的對應bean,或者是否有已經建立好的ObjectFactory,而此時對於A的ObjectFactory咱們早已建立好了,因此便不會再去向後執行,而是直接調用ObjectFactory去獲取A。
到這裏基本能夠理清Spring處理循環依賴的解決辦法,這裏再從代碼層面總結一下:
在建立bean的過程當中,實例化bean結束以後,屬性注入以前,有一段這樣的代碼(代碼位置爲AbstractAutowireCapableBeanFactory類中的doCreateBean()方法中bean實例化以後):
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isDebugEnabled()) { logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } addSingletonFactory(beanName, new ObjectFactory<Object>() { public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } }); }
這段代碼前面也說過,主要作的事情是在addSingletonFactory()方法中,即在必要的時候將建立bean的ObjectFactory添加到緩存中。再結合前面的例子來看,在第一次建立beanA時,這裏是會將ObjectFactory加入到singletonFactories中,當建立beanB時,在對beanB的屬性注入時又會調用getBean()去獲取beanA,一樣是前面說到過,會先去緩存獲取beanA,這時候是能夠獲取到剛纔放到緩存中的ObjectFactory的,這時候就會把實例化好可是還未完成屬性注入的beanA找出來注入到beanB中去,這樣就解決了循環依賴的問題,須要結合下面的代碼細品一下。
protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name); Object bean; // Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); ... } protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }
對於"prototype"做用域的bean,Spring容器並不會對其進行緩存,所以沒法提早暴露一個建立中的bean,因此也是經過拋出異常的方式來處理循環依賴,這裏仍然是用一個demo來測試一下代碼是在哪拋的異常。
配置文件:
<bean id = "testA" class = "xxx" scope = "prototype"> <property name = "testB" ref = "testB"/> </bean> <bean id = "testB" class = "xxx"> <property name = "testC" ref = "testC"/> </bean> <bean id = "testC" class = "xxx"> <property name = "testA" ref = "testA"/> </bean>
測試代碼:
public static void main(String[] args) { try{ ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); System.out.println(ctx.getBean("testA")); }catch (Exception e){ e.printStackTrace(); } }
一樣經過斷點咱們能夠定位異常的拋出位置是在AbstractBeanFactory類的doGetBean方法中,在方法開始獲取緩存失敗以後(prototype不會加入到緩存中),會首先判斷prototype的bean是否已建立,若是是就認爲存在循環依賴,拋出BeanCurrentlyInCreationException異常。
if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); }
Spring中對於循環依賴的處理存在3中場景:
其中對於構造器和prototype範圍的循環依賴,Spring是直接拋出異常。而對於單例的setter循環依賴,Spring是經過在bean加載過程當中提早將bean的ObjectFactory加入到singletonFactories這個緩存用的map中來解決循環依賴的。