Spring源碼初探-IOC(4)-Bean的初始化-循環(huán)依賴的解決

前言

在實際工作中,經(jīng)常由于設(shè)計不佳或者各種因素,導(dǎo)致類之間相互依賴。這些類可能單獨使用時不會出問題,但是在使用Spring進行管理的時候可能就會拋出BeanCurrentlyInCreationException等異常 。當拋出這種異常時表示Spring解決不了該循環(huán)依賴,本文將簡要說明Spring對于循環(huán)依賴的解決方法。

循環(huán)依賴的產(chǎn)生和解決的前提

循環(huán)依賴的產(chǎn)生可能有很多種情況,例如:

A的構(gòu)造方法中依賴了B的實例對象,同時B的構(gòu)造方法中依賴了A的實例對象

A的構(gòu)造方法中依賴了B的實例對象,同時B的某個field或者setter需要A的實例對象,以及反之

A的某個field或者setter依賴了B的實例對象,同時B的某個field或者setter依賴了A的實例對象,以及反之

當然,Spring對于循環(huán)依賴的解決不是無條件的,首先前提條件是針對scope單例并且沒有顯式指明不需要解決循環(huán)依賴的對象,而且要求該對象沒有被代理過。同時Spring解決循環(huán)依賴也不是萬能,以上三種情況只能解決兩種,第一種在構(gòu)造方法中相互依賴的情況Spring也無力回天。結(jié)論先給在這,下面來看看Spring的解決方法,知道了解決方案就能明白為啥第一種情況無法解決了。

Spring對于循環(huán)依賴的解決

Spring循環(huán)依賴的理論依據(jù)其實是Java基于引用傳遞,當我們獲取到對象的引用時,對象的field或者或?qū)傩允强梢匝雍笤O(shè)置的。

Spring單例對象的初始化其實可以分為三步:

createBeanInstance, 實例化,實際上就是調(diào)用對應(yīng)的構(gòu)造方法構(gòu)造對象,此時只是調(diào)用了構(gòu)造方法,spring xml中指定的property并沒有進行populate

populateBean,填充屬性,這步對spring xml中指定的property進行populate

initializeBean,調(diào)用spring xml中指定的init方法,或者AfterPropertiesSet方法

會發(fā)生循環(huán)依賴的步驟集中在第一步和第二步。

三級緩存

對于單例對象來說,在Spring的整個容器的生命周期內(nèi),有且只存在一個對象,很容易想到這個對象應(yīng)該存在Cache中,Spring大量運用了Cache的手段,在循環(huán)依賴問題的解決過程中甚至使用了“三級緩存”。

“三級緩存”主要是指

/** Cache of singleton objects: bean name --> bean instance */private finalMap singletonObjects =newConcurrentHashMap(256);/** Cache of singleton factories: bean name --> ObjectFactory */private finalMap> singletonFactories =newHashMap>(16);/** Cache of early singleton objects: bean name --> bean instance */private finalMap earlySingletonObjects =newHashMap(16);

從字面意思來說:singletonObjects指單例對象的cache,singletonFactories指單例對象工廠的cache,earlySingletonObjects指提前曝光的單例對象的cache。以上三個cache構(gòu)成了三級緩存,Spring就用這三級緩存巧妙的解決了循環(huán)依賴問題。

解決方法

回想上篇文章中關(guān)于Bean創(chuàng)建的過程,首先Spring會嘗試從緩存中獲取,這個緩存就是指singletonObjects,主要調(diào)用的方法是:

protectedObjectgetSingleton(String beanName,booleanallowEarlyReference){? 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);}

首先解釋兩個參數(shù):

isSingletonCurrentlyInCreation 判斷對應(yīng)的單例對象是否在創(chuàng)建中,當單例對象沒有被初始化完全(例如A定義的構(gòu)造函數(shù)依賴了B對象,得先去創(chuàng)建B對象,或者在populatebean過程中依賴了B對象,得先去創(chuàng)建B對象,此時A處于創(chuàng)建中)

allowEarlyReference 是否允許從singletonFactories中通過getObject拿到對象

分析getSingleton的整個過程,Spring首先從singletonObjects(一級緩存)中嘗試獲取,如果獲取不到并且對象在創(chuàng)建中,則嘗試從earlySingletonObjects(二級緩存)中獲取,如果還是獲取不到并且允許從singletonFactories通過getObject獲取,則通過singletonFactory.getObject()(三級緩存)獲取。如果獲取到了則

this.earlySingletonObjects.put(beanName,singletonObject);this.singletonFactories.remove(beanName);

則移除對應(yīng)的singletonFactory,將singletonObject放入到earlySingletonObjects,其實就是將三級緩存提升到二級緩存中!

Spring解決循環(huán)依賴的訣竅就在于singletonFactories這個cache,這個cache中存的是類型為ObjectFactory,其定義如下:

publicinterfaceObjectFactory{TgetObject()throwsBeansException;}

在bean創(chuàng)建過程中,有兩處比較重要的匿名內(nèi)部類實現(xiàn)了該接口。一處是

newObjectFactory() {@OverridepublicObjectgetObject()throwsBeansException{try{returncreateBean(beanName, mbd, args);? ? ? }catch(BeansException ex) {? ? ? ? destroySingleton(beanName);throwex;? ? ? }? }

在上文已經(jīng)提到,Spring利用其創(chuàng)建bean(這樣做真的很不明確呀...)

另一處就是:

addSingletonFactory(beanName,newObjectFactory() {@OverridepublicObjectgetObject()throwsBeansException{returngetEarlyBeanReference(beanName, mbd, bean);? }});

此處就是解決循環(huán)依賴的關(guān)鍵,這段代碼發(fā)生在createBeanInstance之后,也就是說單例對象此時已經(jīng)被創(chuàng)建出來的。這個對象已經(jīng)被生產(chǎn)出來了,雖然還不完美(還沒有進行初始化的第二步和第三步),但是已經(jīng)能被人認出來了(根據(jù)對象引用能定位到堆中的對象),所以Spring此時將這個對象提前曝光出來讓大家認識,讓大家使用。

這樣做有什么好處呢?讓我們來分析一下“A的某個field或者setter依賴了B的實例對象,同時B的某個field或者setter依賴了A的實例對象”這種循環(huán)依賴的情況。A首先完成了初始化的第一步,并且將自己提前曝光到singletonFactories中,此時進行初始化的第二步,發(fā)現(xiàn)自己依賴對象B,此時就嘗試去get(B),發(fā)現(xiàn)B還沒有被create,所以走create流程,B在初始化第一步的時候發(fā)現(xiàn)自己依賴了對象A,于是嘗試get(A),嘗試一級緩存singletonObjects(肯定沒有,因為A還沒初始化完全),嘗試二級緩存earlySingletonObjects(也沒有),嘗試三級緩存singletonFactories,由于A通過ObjectFactory將自己提前曝光了,所以B能夠通過ObjectFactory.getObject拿到A對象(雖然A還沒有初始化完全,但是總比沒有好呀),B拿到A對象后順利完成了初始化階段1、2、3,完全初始化之后將自己放入到一級緩存singletonObjects中。此時返回A中,A此時能拿到B的對象順利完成自己的初始化階段2、3,最終A也完成了初始化,長大成人,進去了一級緩存singletonObjects中,而且更加幸運的是,由于B拿到了A的對象引用,所以B現(xiàn)在hold住的A對象也蛻變完美了!一切都是這么神奇!!

知道了這個原理時候,肯定就知道為啥Spring不能解決“A的構(gòu)造方法中依賴了B的實例對象,同時B的構(gòu)造方法中依賴了A的實例對象”這類問題了!

總結(jié)

Spring通過三級緩存加上“提前曝光”機制,配合Java的對象引用原理,比較完美地解決了某些情況下的循環(huán)依賴問題!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容