Spring 循環依賴

1. 什麼是循環依賴?

循環依賴其實就是循環引用,也就是兩個或者兩個以上的bean互相持有對方,最終形成閉環。比如A依賴於B,B依賴於C,C又依賴於A。如下圖:

uploading.4e448015.gif正在上傳…重新上傳取消

注意,這裏不是函數的循環調用,是對象的相互依賴關係。循環調用其實就是一個死循環,除非有終結條件。

Spring中循環依賴場景有: 

(1)構造器的循環依賴 

(2)field屬性的循環依賴

其中,構造器的循環依賴問題無法解決,只能拋出BeanCurrentlyInCreationException異常,在解決屬性循環依賴時,spring採用的是提前暴露對象的方法。

2. 怎麼檢測是否存在循環依賴

檢測循環依賴相對比較容易,Bean在創建的時候可以給該Bean打標,如果遞歸調用回來發現正在創建中的話,即說明了循環依賴了。

3. Spring怎麼解決循環依賴

Spring的循環依賴的理論依據基於Java的引用傳遞,當獲得對象的引用時,對象的屬性是可以延後設置的。(但是構造器必須是在獲取引用之前)

Spring的單例對象的初始化主要分爲三步: 

(1)createBeanInstance:實例化,其實也就是調用對象的構造方法實例化對象

(2)populateBean:填充屬性,這一步主要是多bean的依賴屬性進行填充

(3)initializeBean:調用spring xml中的init 方法。

從上面單例bean的初始化可以知道:循環依賴主要發生在第一、二步,也就是構造器循環依賴和field循環依賴。那麼我們要解決循環引用也應該從初始化過程着手,對於單例來說,在Spring容器整個生命週期內,有且只有一個對象,所以很容易想到這個對象應該存在Cache中,Spring爲了解決單例的循環依賴問題,使用了三級緩存。

這三級緩存分別指: 

singletonFactories : 單例對象工廠的cache 

earlySingletonObjects :提前暴光的單例對象的Cache 

singletonObjects:單例對象的cache

在創建bean的時候,首先想到的是從cache中獲取這個單例的bean,這個緩存就是singletonObjects。如果獲取不到,並且對象正在創建中,就再從二級緩存earlySingletonObjects中獲取。如果還是獲取不到且允許singletonFactories通過getObject()獲取,就從三級緩存singletonFactory.getObject()(三級緩存)獲取,如果獲取到了則:從singletonFactories中移除,並放入earlySingletonObjects中。其實也就是從三級緩存移動到了二級緩存。

從上面三級緩存的分析,我們可以知道,Spring解決循環依賴的訣竅就在於singletonFactories這個三級cache。這個cache的類型是ObjectFactory。這裏就是解決循環依賴的關鍵,發生在createBeanInstance之後,也就是說單例對象此時已經被創建出來(調用了構造器)。這個對象已經被生產出來了,雖然還不完美(還沒有進行初始化的第二步和第三步),但是已經能被人認出來了(根據對象引用能定位到堆中的對象),所以Spring此時將這個對象提前曝光出來讓大家認識,讓大家使用。

這樣做有什麼好處呢?讓我們來分析一下“A的某個field或者setter依賴了B的實例對象,同時B的某個field或者setter依賴了A的實例對象”這種循環依賴的情況。A首先完成了初始化的第一步,並且將自己提前曝光到singletonFactories中,此時進行初始化的第二步,發現自己依賴對象B,此時就嘗試去get(B),發現B還沒有被create,所以走create流程,B在初始化第一步的時候發現自己依賴了對象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現在hold住的A對象完成了初始化。

知道了這個原理時候,肯定就知道爲啥Spring不能解決“A的構造方法中依賴了B的實例對象,同時B的構造方法中依賴了A的實例對象”這類問題了!因爲加入singletonFactories三級緩存的前提是執行了構造器,所以構造器的循環依賴沒法解決。

4.基於構造器的循環依賴

Spring容器會將每一個正在創建的Bean 標識符放在一個“當前創建Bean池”中,Bean標識符在創建過程中將一直保持在這個池中,因此如果在創建Bean過程中發現自己已經在“當前創建Bean池”裏時將拋出BeanCurrentlyInCreationException異常表示循環依賴;而對於創建完畢的Bean將從“當前創建Bean池”中清除掉。

Spring容器先創建單例A,A依賴B,然後將A放在“當前創建Bean池”中,此時創建B,B依賴C ,然後將B放在“當前創建Bean池”中,此時創建C,C又依賴A, 但是,此時A已經在池中,所以會報錯,因爲在池中的Bean都是未初始化完的,所以會依賴錯誤 ,(初始化完的Bean會從池中移除)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章