天天看點

Spring怎麼用三級緩存解決循環依賴問題

作者:馬士兵教育CTO
Spring怎麼用三級緩存解決循環依賴問題

循環依賴問題

A類中有b對象,B類中有a對象,互相引用,

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

在spring整個生命周期裡面,所有bean預設都是單例的,通過反射建立一個具體的對象,設定對象屬性值。

目前這種情況,要麼先建立a,要麼先建立b。

如果a對象建立好了,b對象還沒有建立,此時需要把b對象也完成執行個體化,才能給a對象完成一個指派。

在spring中除了執行個體化,還有一個初始化 ,這裡面就會牽扯到一個循環引用的問題。

怎麼解決循環依賴的問題?

所謂執行個體化,就是在堆中開辟一塊空間,執行個體化要麼通過反射要麼new的方式。

初始化的時候給某些類的屬性進行指派,設定屬性有2種方式,set和構造方法,

set是額外的一個獨立的方法,用來設定屬性值;

構造方法是既建立了對象又給屬性指派。

set可以解決循環依賴問題,

構造方法不能解決循環依賴。

set先有對象,再給對象set即分成2步。

構造方法建立出對象之後立馬給對象屬性值指派,将兩步合成一步。

把步驟分開可以解決這個問題,步驟合起來是沒有辦法解決這個問題的。

當使用三級緩存解決循環依賴問題的時候,本質的點在于将執行個體化和初始化分開處理。

提前暴露對象 :已經完成執行個體化但是未完成初始化的對象。

Spring怎麼用三級緩存解決循環依賴問題

把這兩個類放入spring之後,會幫我們進行具體對象的建立,建立的時候對象都是單例的。

單例意味着整個空間中有且僅有這一個對象。

兩個bean不可能同時建立,而是一個一個來建立的。

第一步先執行個體化a對象,此時隻是堆空間開辟,并沒有設定屬性值,此時不是一個完整的對象,是半成品,當有了半成品之後,要初始化a對象,就是給a對象中的b屬性完成指派操作,此時要給b屬性指派了,b是一個完全獨立的對象,是以此時要去beanfactory或spring容器中查找b對象,

Spring怎麼用三級緩存解決循環依賴問題

判斷是否有b對象,如果有直接指派,沒有的話,要完成b對象的執行個體化,

Spring怎麼用三級緩存解決循環依賴問題

先執行個體化b對象,如果能成功,說明隻是在堆中配置設定了一塊記憶體空間,并沒有設定一個具體的屬性值,這裡也是半成品,接下來要完成b對象的初始化操作即給b對象中的a屬性指派,去spring容器中查找a對象,如果有的話,直接指派傳回;如果沒有怎麼辦?再次去執行個體化a,

Spring怎麼用三級緩存解決循環依賴問題

此時就變成一個環了,永遠結束不了。

此時找a對象的時候是否需要把a再重新執行個體化取決于目前容器裡面是否有a,此時a已經完成了執行個體化,因為之前已經用過了,隻不過它不是一個完整的對象,隻是一個半成品對象,既然是半成品能不能在半成品裡面直接擷取到?

直接可以把對應的半成品放到spring容器裡面來,半成品放進來之後,會思考一件事情,裡面沒有屬性還不能使用,就意味着在後續的一個處理過程中,最終還是要把目前的a對象完成初始化操作的。

此時最起碼已經找到半成品了或者容器裡面已經有對象了,隻不過這個對象還不能直接拿過來使用,還要進行相關的其他操作。

怎麼把半成品放到容器裡面?

Spring怎麼用三級緩存解決循環依賴問題

為什麼要設計三級緩存的存在? 這三個緩存有什麼不同的點?

除了容量和類型之外,不同的地方在于整個集合的範型,一級緩存和二級緩存是Object,三級緩存裡面放的是ObjectFactory,

Spring怎麼用三級緩存解決循環依賴問題

ObjectFactory是函數式接口,僅有一個方法,可以往裡面傳入一個lamda表達式或傳入一個匿名内部類,可以通過getObject方法執行相關的具體邏輯,

refresh包含的13個方法是spring處理的核心,循環依賴涉及到bean的執行個體化過程,是以進行相關執行個體化的時候,要看finishBeanFactoryInitialization(beanFactory)方法,調用beanFactory.preInstantiateSingletons()。

通過debug的方式看bean執行個體化的過程

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

當開始執行個體化的時候,并沒有指定要執行個體化幾個bean,scope預設是單例(singleton),

當執行到finishBeanFactoryInitialization(beanFactory)這一步的時候,配置檔案中的2個bean已經被加載了,放到了beanDefinitionNames map集合中,

Spring怎麼用三級緩存解決循環依賴問題

從這行開始進行執行個體化,

Spring怎麼用三級緩存解決循環依賴問題

目前空間裡包含了2個對象,是以此時要去循環的進行建立。

要麼先a後b,要麼先b後a,

若a先執行個體化,a先擷取BeanDefinition,判斷下是否是抽象的(a不是抽象的),判斷下是否是單例的(a是單例的),是否是懶加載的(a不是懶加載的),

Spring怎麼用三級緩存解決循環依賴問題

是以條件為true,再判斷有沒有實作FactoryBean接口,沒有,則執行getBean方法(spring在建立對象之前,每次都是先從容器中查找,找不到再建立),

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

要去容器裡面進行一個查找工作,先去一級緩存進行查找,判斷目前這個單例對象是否在被建立的過程當中,

a此時還沒有開始建立,是以條件為false。

Spring怎麼用三級緩存解決循環依賴問題

lambda表達式和匿名内部類是一樣的,裡面最核心的方法是createBean,

Spring怎麼用三級緩存解決循環依賴問題

先從一級緩存singletonObjects中擷取,一級緩存沒有的話,調用lambda表,當你看到getObject的時候,它實際上調用的createBean,

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

執行到這裡,就擷取到了a對象了(A@1755),隻是b屬性是null即還沒有給b屬性指派,此時的bean對象是一個半成品,還沒有進行初始化。

當a執行個體化完成之後,要完成整體對象的填充即屬性填充(給b填充屬性值),

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

隻有調用getObject才會執行lamda表達式。

先判斷一級緩存singletonObjects是否包含a(此時不包含),條件為true,将a對應的lambda表達式放三級緩存singletonFactories中,

Spring怎麼用三級緩存解決循環依賴問題

registeredSingletons這個集合表示哪些對象已經注冊過了。

Spring怎麼用三級緩存解決循環依賴問題

這一步就是完整的屬性填充方法,

Spring怎麼用三級緩存解決循環依賴問題

開始填充屬性值,給a對象裡面的b填充屬性,

Spring怎麼用三級緩存解決循環依賴問題

此時屬性名稱是b,屬性值不是bean對象,而是運作時的bean引用,

Spring怎麼用三級緩存解決循環依賴問題

對目前這個值進行相關的處理工作,

Spring怎麼用三級緩存解決循環依賴問題

判斷value是什麼類型,進行相應的解析,

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

去容器中找b,對應着這一步,

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

一級緩存沒有b對象,b對象也沒有在建立,條件不滿足,直接傳回。

接下來,執行個體化b對象,

Spring怎麼用三級緩存解決循環依賴問題

調用createBean->doCreateBean方法,

Spring怎麼用三級緩存解決循環依賴問題

執行個體化b,

Spring怎麼用三級緩存解決循環依賴問題

此時已經有了b對象了,還沒有給b初始化,是以a屬性是空的,此時b是半成品對象,

Spring怎麼用三級緩存解決循環依賴問題

此時這兩個對象都是半成品對象,隻完成了執行個體化并沒有初始化操作,

Spring怎麼用三級緩存解決循環依賴問題

此時b已經完成了執行個體化,接下來給b填充屬性,

Spring怎麼用三級緩存解決循環依賴問題

往三級緩存加b的lamda函數,

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

給b對象裡面的a屬性填充,因為a還是空的,

Spring怎麼用三級緩存解決循環依賴問題

從容器中擷取a,對應着這一步,

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

截止目前,已經是第3次調用getBean方法了:

第一次建立a的時候,第二次建立b的時候,第三次給b裡面的a屬性指派的時候,

Spring怎麼用三級緩存解決循環依賴問題

一級緩存中沒有,a對象現在是在被建立過程中,再從二級緩存再擷取,二級緩存沒有,去三級緩存中擷取,

根據a去三級緩存中擷取到a所對應的lambda表達式,調用getObject方法,實際上執行的是lambda表達式,

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

這裡的bean對象是A@1755,屬性b為null,這個時候取到a對象了,隻不過此時的a對象是一個半成品對象,然後删除三級緩存中的a對應的lambda表達式,

Spring怎麼用三級緩存解決循環依賴問題

剛才是為了給b裡面的a屬性指派:先執行個體化a再初始化a,初始化a的時候找b,先執行個體化b,再初始化b,初始化b的時候要去容器裡面找a,a找回來了,指派給b中的a屬性,當指派完成之後,相當于把a的半成品取回來,完成了整體的指派操作,意味着b的執行個體化和初始化都完成了。

一開始為了給a進行初始化,現在b完成了,該去初始化a了,此時a是半成品狀态,b是成品狀态。

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

往一級緩存放入對象B@2152,

Spring怎麼用三級緩存解決循環依賴問題

然後把二級和三級緩存删掉,

Spring怎麼用三級緩存解決循環依賴問題

然後給a對象的b屬性指派,

Spring怎麼用三級緩存解決循環依賴問題

執行完這一步就得到了A@1755,屬性b為B@2151,現在完成了a對象的初始化工作,a是成品對象了,然後放入一級緩存,把二級三級緩存清理掉,

Spring怎麼用三級緩存解決循環依賴問題
Spring怎麼用三級緩存解決循環依賴問題

第一次for循環是為了建立a,此時a對象執行個體化完成了,第二次循環是要執行個體化b了,而此時一級緩存已經有b了,直接傳回b對象。

小結

三級緩存解決循環依賴問題的關鍵是通過提前暴露對象來解決,就在于執行個體化和初始化分開操作,在中間過程中給其他對象指派的并不是一個完整的對象,而是半成品對象。

先執行個體化a,再初始化a,然後再執行個體化b,再初始化b,要找到對應的a,此時取的a是一個半成品狀态,是以就解決了循環依賴的問題。