本文主要是分析Spring bean的循環(huán)依賴,以及Spring的解決方式啥刻。 通過這種解決方式奸鸯,我們可以應用在我們實際開發(fā)項目中。
什么是循環(huán)依賴可帽?
怎么檢測循環(huán)依賴
Spring怎么解決循環(huán)依賴
Spring對于循環(huán)依賴無法解決的場景
Spring解決循環(huán)依賴的方式我們能夠?qū)W到什么娄涩?
一. 什么是循環(huán)依賴?
循環(huán)依賴其實就是循環(huán)引用,也就是兩個或則兩個以上的bean互相持有對方蓄拣,最終形成閉環(huán)扬虚。比如A依賴于B,B依賴于C球恤,C又依賴于A辜昵。如下圖:
注意,這里不是函數(shù)的循環(huán)調(diào)用咽斧,是對象的相互依賴關(guān)系堪置。循環(huán)調(diào)用其實就是一個死循環(huán),除非有終結(jié)條件张惹。
Spring中循環(huán)依賴場景有:
構(gòu)造器的循環(huán)依賴
field屬性的循環(huán)依賴
二. 怎么檢測是否存在循環(huán)依賴舀锨?
檢測循環(huán)依賴相對比較容易,Bean在創(chuàng)建的時候可以給該Bean打標宛逗,如果遞歸調(diào)用回來發(fā)現(xiàn)正在創(chuàng)建中的話坎匿,即說明了循環(huán)依賴了。
三. Spring怎么解決循環(huán)依賴
Spring的循環(huán)依賴的理論依據(jù)其實是基于Java的引用傳遞雷激,當我們獲取到對象的引用時替蔬,對象的field或則屬性是可以延后設(shè)置的(但是構(gòu)造器必須是在獲取引用之前)。
Spring的單例對象的初始化主要分為三步:
createBeanInstance:實例化屎暇,其實也就是調(diào)用對象的構(gòu)造方法實例化對象
populateBean:填充屬性承桥,這一步主要是多bean的依賴屬性進行填充
initializeBean:調(diào)用spring xml中的init 方法。
從上面講述的單例bean初始化步驟我們可以知道根悼,循環(huán)依賴主要發(fā)生在第一快毛、第二步。也就是構(gòu)造器循環(huán)依賴和field循環(huán)依賴番挺。
那么我們要解決循環(huán)引用也應該從初始化過程著手唠帝,對于單例來說,在Spring容器整個生命周期內(nèi)玄柏,有且只有一個對象襟衰,所以很容易想到這個對象應該存在Cache中,Spring為了解決單例的循環(huán)依賴問題粪摘,使用了三級緩存瀑晒。
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
這三級緩存分別指:
singletonObjects:單例對象的cache
earlySingletonObjects :提前暴光的單例對象的Cache
singletonFactories : 單例對象工廠的cache
我們在創(chuàng)建bean的時候,首先想到的是從cache中獲取這個單例的bean徘意,這個緩存就是singletonObjects苔悦。主要調(diào)用方法就就是:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
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;
}
上面的代碼需要解釋兩個參數(shù):
isSingletonCurrentlyInCreation():判斷當前單例bean是否正在創(chuàng)建中,也就是沒有初始化完成(比如A的構(gòu)造器依賴了B對象所以得先去創(chuàng)建B對象椎咧, 或則在A的populateBean過程中依賴了B對象玖详,得先去創(chuàng)建B對象把介,這時的A就是處于創(chuàng)建中的狀態(tài)。)
allowEarlyReference :是否允許從singletonFactories中通過getObject拿到對象
分析getSingleton()的整個過程蟋座,Spring首先從一級緩存singletonObjects中獲取拗踢。如果獲取不到,并且對象正在創(chuàng)建中向臀,就再從二級緩存earlySingletonObjects中獲取巢墅。如果還是獲取不到且允許singletonFactories通過getObject()獲取,就從三級緩存singletonFactory.getObject()(三級緩存)獲取券膀,如果獲取到了則:
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
從singletonFactories中移除君纫,并放入earlySingletonObjects中。其實也就是從三級緩存移動到了二級緩存芹彬。
從上面三級緩存的分析庵芭,我們可以知道,Spring解決循環(huán)依賴的訣竅就在于singletonFactories這個三級cache雀监。這個cache的類型是ObjectFactory,定義如下:
public interface ObjectFactory<T> {
/**
* Return an instance (possibly shared or independent)
* of the object managed by this factory.
* @return the resulting instance
* @throws BeansException in case of creation errors
*/
T getObject() throws BeansException;
}
這個接口在下面被引用
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
這里就是解決循環(huán)依賴的關(guān)鍵眨唬,這段代碼發(fā)生在createBeanInstance之后会前,也就是說單例對象此時已經(jīng)被創(chuàng)建出來(調(diào)用了構(gòu)造器)。這個對象已經(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的實例對象”這類問題了嗅剖!因為加入singletonFactories三級緩存的前提是執(zhí)行了構(gòu)造器,所以構(gòu)造器的循環(huán)依賴沒法解決嘁扼。