先了解幾個概念:
singletonObjects:單例池,也稱之為一級緩存。 earlySingletonObjects:未完成初始化的單例池,也稱之為二級緩存 。 singletonFactories:三級緩存,實際上存放“創(chuàng)建對象的lambda表達式”(如果是普通對象直接返回,如果需要AOP則執(zhí)行l(wèi)ambda表達式創(chuàng)建一個代理對象。 原始對象:未經(jīng)過完整生命周期的bean。代理對象:如果Spring掃描時發(fā)現(xiàn)某個對象需要切面,Spring最終會經(jīng)過AOP為其創(chuàng)建一個代理對象,并放入單例池中。)
什么是循環(huán)依賴?
簡單來說就是對象A依賴了對象B,而對象B又依賴了對象A,如下面代碼:
// A -> B
class A{
public B b;
}
// B -> A
class B{
public A a;
}
循環(huán)依賴帶來的影響
實際上,如果對象不是放在在Spring容器中管理,上面代碼是沒有所謂循環(huán)依賴問題,可以正常運行。而循環(huán)依賴之所以產(chǎn)生,是因為Spring中的bean創(chuàng)建要經(jīng)過一定的生命周期。
A與B的bean創(chuàng)建流程:
- 假設(shè)A先被掃描到,于是先執(zhí)行A的bean生命周期:
1、實例化A得到一個原始對象
2、給A中的B屬性賦值,于是執(zhí)行B的bean生命周期
...
初始化后,可能進行AOP
- 執(zhí)行B的bean生命周期:
1、實例化B得到一個原始對象
2、給B中的A屬性賦值,于是去執(zhí)行A的bean生命周期
...
初始化后,可能進行AOP
按照常規(guī)邏輯,當上面A的生命周期執(zhí)行到第2步時,會去執(zhí)行B的生命周期,而B的生命周期執(zhí)行到第2步又需要一個A對象,于是又會去走A的生命周期。。此時便出現(xiàn)循環(huán)依賴問題了,如下圖所示。

三級緩存之 “第二級緩存“:earlySingletonObjects
順著上面的思路,如果由我們來設(shè)計Spring,會如何設(shè)計解決上面這種循環(huán)依賴的問題?加一個中間緩存(二級緩存,Spring中對應(yīng)名稱為earlySingletonObjects)似乎就可以解決,思路如下:
在創(chuàng)建完A原始對象后,會將其放入earlySingletonObjects緩存中(提早暴露給其它的bean),然后注入依賴B,此時發(fā)現(xiàn)B在singletonObjects單例池中(圖中省略)不存在且earlySingletonObjects也不存在,于是創(chuàng)建B的bean,創(chuàng)建B的bean過程也和創(chuàng)建A一樣,會先創(chuàng)建B的原始對象并放入earlySingletonObjects中進行暴露,然后注入依賴A,此時發(fā)現(xiàn)A不存在于singletonObjects,但A的原始對象存在于singletonsCurrentlyInCreation(說明A的bean也正在創(chuàng)建中),于是從earlySingletonObjects找,可以找到一個原始對象,B完成依賴注入并實例化后,A也可以完成依賴注入。

那么上面這樣處理方式,是否就能解決循環(huán)依賴問題了?咋一看似乎滿足了,但實際上下面場景就會打破上面這種平衡。
場景:如果A的原始對象注入給B的屬性之后,A的原始對象進行了AOP(按照bean的生命周期,AOP是在初始化后在后置處理器中處理的),此時會產(chǎn)生另外一個對象-代理對象,這個對象最終會被存放到單例池singletonObjects中,也就是說,對于A而言,它最終的bean對象實際上應(yīng)該是AOP之后的代理對象而不是原來那個原始對象,但B拿的是原始對象,這就產(chǎn)生沖突:B依賴的A和最終的A不是同一個對象。(這也回答了面試題:解決Spring循環(huán)依賴問題只用一二級緩存行不行?)
三級緩存之 “第三級緩存“:singletonFactories
那么如何解決B依賴的A和最終的A不是同一個對象這種沖突?
方式1:不管有沒有循環(huán)依賴問題,統(tǒng)一在初始化前AOP?這種做法明顯破壞了Spring所設(shè)定的bean生命周期,如果為了解決循環(huán)依賴問題去破壞bean生命周期的設(shè)計原則,那么得不償失。
方式2:判斷是否依賴的對象也在創(chuàng)建,如果是才提前對該對象進行AOP,并將AOP代理對象存放起來,后續(xù)該對象在初始化后,判斷如果對象已被代理過,是則不再進行AOP。
對比發(fā)現(xiàn)顯然第二種方式更加合適,Spring也確實采用了第二種方式來解決沖突,于是三級緩存:singletonFactories派上用場。就像其名一樣,singletonFactories是一個單例工廠。如果我們打開Spring源碼,可以看到它的值存的是一個beanName → lambda表達式,這個lambda表達式可能用到也可能用不到。
- 何時存入lambda表達式?
存入步驟是在創(chuàng)建完原始對象之后執(zhí)行的,Spring中是在AbstractAutowireCapableBeanFactory.doCreateBean() → getSingleTon() 中進行存儲。

- 什么時候執(zhí)行l(wèi)ambda表達式?
獲取bean的邏輯,走的是DefaultSingletonBeanRegistry.getBean(),當getBean調(diào)用到getSingleton()時,會進入下面代碼。從代碼可以看出,只有當1級、2級緩存拿不到bean時,且存在循環(huán)依賴問題(通過代碼isSingletonCurrentlyInCreation(beanName)判斷)時,才會去執(zhí)行l(wèi)ambda表達式,獲取真正的bean(普通原始對象或代理對象),并將其放入二級緩存earlySingletonObjects中,同時刪除三級緩存中的引用(注意:此處因為需要保證原子性,前面對singletonObjects加了獨占鎖)。

- lambda中的邏輯是啥?
同樣是在AbstractAutowireCapableBeanFactory類中,可以從getEarlyBeanReference方法中看到,最終lambda表達式執(zhí)行的邏輯,就是去遍歷所有后置處理器,然后在SmartInstantiationAwareBeanPostProcessor的實現(xiàn)類中的getEarlyBeanReference方法中去創(chuàng)建bean,而創(chuàng)建的bean可能是普通原始對象 (在InstantiationAwareBeanPostProcessorAdapter中,直接返回當前bean),也可能是代理對象(也就是提前AOP,在 AbstractAutoProxyCreator中通過jdk動態(tài)代理或cglib創(chuàng)建),需要注意的是無論是普通對象還是代理對象,都不是經(jīng)過完整生命周期的最終對象,因此在執(zhí)行l(wèi)ambda表達式后不能將返回的bean直接放入一級緩存,即單例池singletonObjects中,而是要先放到二級緩存提前暴露給其他bean,只有經(jīng)過所有后置處理器BeanPostProcesssor后才會真正放入singletonObjects中。

- 如果bean被提前AOP,初始化后的AOP邏輯中,要如何判斷不需再進行AOP?
當bean初始化后,后置處理器AbstractAutoProxyCreator中的postProcessAfterInitialization方法會被調(diào)用,其中存在這么一段邏輯,即判斷earlyProxyReferences(又是一個cache,但一般不會把它計入三級緩存中)中是否已有該cache,有則說明已經(jīng)進行過AOP,那么就不會再次進行AOP。(wrapIfNecessary中還有更多細節(jié)的判斷是否需要AOP,此處相當于提前截斷,不需要再進行細節(jié)判斷了)

什么時候下Spring解決不了循環(huán)依賴?
- 情況一:使用構(gòu)造器注入
Spring在創(chuàng)建原始對象的原理也是調(diào)用構(gòu)造器進行創(chuàng)建,如果使用構(gòu)造器進行注入,那么Spring自身無法解決循環(huán)依賴問題,此時加入@Lazy注解即可,原理是對于Lazy修飾的對象,Spring會先創(chuàng)建一個代理對象給屬性賦值,那么依賴方就可以正常進行實例化了。
class A {
B b;
@Autowired
public void method(B b) {
this.b = b;
}
}
class B {
A a;
@Autowired
public void method(A a) {
this.a = a;
}
}
- 情況二:循環(huán)依賴且用@Async注解修飾方法
Spring在掃描bean、發(fā)現(xiàn)某個類方法被@Async修飾時,會通過后置處理器AsyncAnnotationBeanPostProcessor生成代理對象,而該后置處理器的順序比處理AOP的后置處理器還靠后,因此仍然會導(dǎo)致Spring處理不了循環(huán)依賴。同理,可以使用@Lazy注解解決該問題。
class A {
@Autowired
B b;
@Async
public void method() {
b.xx();
}
}
class B {
A a;
}