淺談Spring中的循環(huán)依賴

什么是循環(huán)依賴?

????之前提到了在進(jìn)行創(chuàng)建單例Bean的時(shí)候有個(gè)類(lèi)參數(shù)singletonCurrentlyInCreation,這個(gè)參數(shù)是用來(lái)記錄當(dāng)前正在進(jìn)行實(shí)例化的beanName。此外還定義了三個(gè)存放不同實(shí)例的緩存:

? ? 一級(jí)緩存:singletonObjects,用來(lái)存放以beanName為key,對(duì)應(yīng)已完成bean生命周期的bean實(shí)例作為value;

? ? 二級(jí)緩存:earlySingletonObjects,里面存放的都是提前暴露的單例實(shí)例,此時(shí)bean的生命周期還未結(jié)束;

? ? 三級(jí)緩存:singletonFactories:用來(lái)存放以beanName為key,對(duì)應(yīng)的BeanFactory為value;

? ? 我們先創(chuàng)建如圖的兩個(gè)類(lèi),除了各自引用外再添加一個(gè)參數(shù),用來(lái)跟蹤這個(gè)bean的生命周期進(jìn)行到哪一步了,并重寫(xiě)toString()方法,如圖1、圖2:

圖1
圖2

? ? 執(zhí)行結(jié)果如圖3,但是按照我們寫(xiě)的自定義Spring容器,會(huì)出現(xiàn)A和B不斷的相互引用,最終耗盡了服務(wù)器資源,這就是循環(huán)依賴,Spring就是利用上面幾個(gè)參數(shù)來(lái)解決循環(huán)依賴的問(wèn)題。

圖3

Spring如何解決循環(huán)依賴?

? ? 1、在getBean操作的時(shí)候,進(jìn)入doGetBean方法,執(zhí)行g(shù)etSingleton(beanName)方法,來(lái)嘗試從三個(gè)緩存中拿到對(duì)應(yīng)的實(shí)例,A_BeanName剛進(jìn)來(lái)的時(shí)候三個(gè)緩存中都沒(méi)有A_BeanName的值,在singletonCurrentlyInCreation中也沒(méi)有正在實(shí)例化的beanName,說(shuō)明目前這個(gè)bean還未被創(chuàng)建,如圖4:

圖4

? ? 2、由于循環(huán)依賴只發(fā)生在單例的情況,那么直接看圖5:

圖5

????先關(guān)注下getSingleton操作,和上面獲取實(shí)例的只是個(gè)同名方法而已,進(jìn)來(lái)一共可以分成四部分,如圖6:”beforeSingletonCreation(創(chuàng)建實(shí)例之前的操作)、getObject(創(chuàng)建實(shí)例)、afterSingletonCreation(創(chuàng)建實(shí)例之后的操作)、addSingleton(如果是新生成的單例,把他加進(jìn)緩存)。beforeSingletonCreation會(huì)把當(dāng)前的beanName放進(jìn)singletonCurrentlyInCreation中,如圖7。在調(diào)用getObject的時(shí)候會(huì)調(diào)用到匿名類(lèi)的createBean方法來(lái)創(chuàng)建實(shí)例。

圖6
圖7

? ? 3、關(guān)注doCreateBean,首先調(diào)用createBeanInstance,本次采用的是無(wú)參構(gòu)造方法instantiateBean,把A包裝成A_BeanWrapper,具體的封裝情況參考上節(jié)的getBean流程。通過(guò)斷點(diǎn)可以看到此時(shí)A_BeanWrapper已經(jīng)包含了B,但是沒(méi)有A的其他屬性,B屬性也為null,說(shuō)明A并沒(méi)有創(chuàng)建完成,此時(shí)B進(jìn)行未進(jìn)行依賴注入,如圖8:

圖8

? ? 4、earlySingletonExposure參數(shù)代表是否能進(jìn)行提前暴露,也可以從這看到提前暴露的條件:?jiǎn)卫?、允許循環(huán)依賴、當(dāng)前beanName正處于正在創(chuàng)建Bean的列表中。如圖9:

圖9

????此時(shí)的getEarlyBeanReference方法是用來(lái)收集BeanFactory實(shí)例的,從實(shí)現(xiàn)方法里的getCacheKey中可以看到,工廠Bean前綴特有的&標(biāo)識(shí),如圖10和圖11:

圖10
圖11

????然后調(diào)用addSingletonFactory往三級(jí)緩存中添加A_BeanName和A_BeanFactory,如圖12:

圖12

? ? 5、通過(guò)populateBean進(jìn)行依賴注入,此時(shí)又是一個(gè)BeanPostProcessor的應(yīng)用,postProcessProperties方法對(duì)@Autowired方法進(jìn)行掃描,如圖13:

圖13

????findAutowiringMetadata返回的InjectionMetadata參數(shù)中的injectedElements參數(shù)就是B,如圖14:

圖14

????繼續(xù)調(diào)用其inject方法,關(guān)注element.inject方法,由于@Autowired注解是放在參數(shù)上的,因此訪問(wèn)AutowiredFieldElement中的inject方法,調(diào)用resolveFieldValue,到beanFactory.resolveDependency,到doResolveDependency,到findAutowireCandidates方法獲取到持有@Autowired注解的B,除了往autowiredBeanNames中添加對(duì)應(yīng)的beanName外,調(diào)用descripter.resolveCandidate方法,進(jìn)去發(fā)現(xiàn)是一個(gè)B_BeanName的getBean操作,如圖15、16:

圖15
圖16

? ? 6:、和A一樣,重復(fù)之前的實(shí)例化流程,在singletonCurrentlyInCreation中添加B_BeanName,通過(guò)addSingletonFactory往三級(jí)緩存中添加B_BeanName和B_BeanFactory,然后通過(guò)populateBean操作,最終通過(guò)getBean方法對(duì)A進(jìn)行實(shí)例化。

? ? 7、又再次來(lái)到了第一步的getSingleton方法,此時(shí)singletonCurrentlyInCreation中已經(jīng)有A_BeanName,雖然在一二級(jí)緩存中沒(méi)有對(duì)應(yīng)的實(shí)例,但是三次緩存中有A_BeanFactory,可以通過(guò)getObject拿到提前暴露的實(shí)例,然后把它放進(jìn)二級(jí)緩存,打斷點(diǎn)可以看到此時(shí)的A_SingletonObject中的B類(lèi)屬性為null,如圖17::

圖17

????但是此時(shí)走的就不是else的路線了,關(guān)注getObjectForBeanInstance方法,最終也是返回一個(gè)對(duì)象實(shí)例,如果是工廠實(shí)例,則會(huì)調(diào)用其getObject方法生成對(duì)象實(shí)例返回,期間并沒(méi)有對(duì)其余屬性進(jìn)行諸如,我們?cè)赿oGetBean的return的返回值中可以看到,如圖18:

圖18

? ? 8、步驟2進(jìn)行的getSingleton方法在getObject的方法終于有返回了,如圖19,返回的是B的實(shí)例,可以看到生成的B實(shí)例中已經(jīng)包含了testB屬性,以及提前暴露的A屬性。

圖19

????然后再進(jìn)行afterSingletonCreation方法,把B_BeanName從singletonCurrentlyInCreation中移除,如圖20:

圖20

????并通過(guò)addSingleton,把B_BeanName和B實(shí)例放入一級(jí)緩存后返回,此時(shí)A屬性中的B屬性依舊是null,如圖21:

圖21

? ? 9、從上面的流程看,B實(shí)例化是由A的實(shí)例化中B屬性的依賴注入觸發(fā)getBean完成的,現(xiàn)在B已經(jīng)被實(shí)例化了,因此A的B屬性也可以完成依賴注入了,這樣A中的B屬性也有值。由于B的屬性指向了A實(shí)例的堆空間,所以B類(lèi)中的A屬性也被賦予值了,如圖22:

圖22

? ? 至此,Spring循環(huán)依賴的流程已經(jīng)結(jié)束了。

? ? 自定義Spring容器代碼地址:https://github.com/LuoChen1996/identitify_spring.git

? ? Spring源碼測(cè)試代碼地址:https://github.com/LuoChen1996/my_spring.git

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容