前言
在實(shí)際工作中,經(jīng)常由于設(shè)計(jì)不佳或者各種因素,導(dǎo)致類之間相互依賴。這些類可能單獨(dú)使用時(shí)不會(huì)出問題,但是在使用Spring進(jìn)行管理的時(shí)候可能就會(huì)拋出BeanCurrentlyInCreationException等異常 。當(dāng)拋出這種異常時(shí)表示Spring解決不了該循環(huán)依賴,本文將簡(jiǎn)要說明Spring對(duì)于循環(huán)依賴的解決方法。
循環(huán)依賴的產(chǎn)生和解決的前提
循環(huán)依賴的產(chǎn)生可能有很多種情況,例如:
- A的構(gòu)造方法中依賴了B的實(shí)例對(duì)象,同時(shí)B的構(gòu)造方法中依賴了A的實(shí)例對(duì)象
- A的構(gòu)造方法中依賴了B的實(shí)例對(duì)象,同時(shí)B的某個(gè)field或者setter需要A的實(shí)例對(duì)象,以及反之
- A的某個(gè)field或者setter依賴了B的實(shí)例對(duì)象,同時(shí)B的某個(gè)field或者setter依賴了A的實(shí)例對(duì)象,以及反之
當(dāng)然,Spring對(duì)于循環(huán)依賴的解決不是無條件的,首先前提條件是針對(duì)scope單例并且沒有顯式指明不需要解決循環(huán)依賴的對(duì)象,而且要求該對(duì)象沒有被代理過。同時(shí)Spring解決循環(huán)依賴也不是萬能,以上三種情況只能解決兩種,第一種在構(gòu)造方法中相互依賴的情況Spring也無力回天。結(jié)論先給在這,下面來看看Spring的解決方法,知道了解決方案就能明白為啥第一種情況無法解決了。
Spring對(duì)于循環(huán)依賴的解決
Spring循環(huán)依賴的理論依據(jù)其實(shí)是Java基于引用傳遞,當(dāng)我們獲取到對(duì)象的引用時(shí),對(duì)象的field或者或?qū)傩允强梢匝雍笤O(shè)置的。
Spring單例對(duì)象的初始化其實(shí)可以分為三步:
- createBeanInstance, 實(shí)例化,實(shí)際上就是調(diào)用對(duì)應(yīng)的構(gòu)造方法構(gòu)造對(duì)象,此時(shí)只是調(diào)用了構(gòu)造方法,spring xml中指定的property并沒有進(jìn)行populate
- populateBean,填充屬性,這步對(duì)spring xml中指定的property進(jìn)行populate
- initializeBean,調(diào)用spring xml中指定的init方法,或者AfterPropertiesSet方法
會(huì)發(fā)生循環(huán)依賴的步驟集中在第一步和第二步。
三級(jí)緩存
對(duì)于單例對(duì)象來說,在Spring的整個(gè)容器的生命周期內(nèi),有且只存在一個(gè)對(duì)象,很容易想到這個(gè)對(duì)象應(yīng)該存在Cache中,Spring大量運(yùn)用了Cache的手段,在循環(huán)依賴問題的解決過程中甚至使用了“三級(jí)緩存”。
“三級(jí)緩存”主要是指
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
從字面意思來說:singletonObjects指單例對(duì)象的cache,singletonFactories指單例對(duì)象工廠的cache,earlySingletonObjects指提前曝光的單例對(duì)象的cache。以上三個(gè)cache構(gòu)成了三級(jí)緩存,Spring就用這三級(jí)緩存巧妙的解決了循環(huán)依賴問題。
解決方法
回想上篇文章中關(guān)于Bean創(chuàng)建的過程,首先Spring會(huì)嘗試從緩存中獲取,這個(gè)緩存就是指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 != NULL_OBJECT ? singletonObject : null);}
首先解釋兩個(gè)參數(shù):
- isSingletonCurrentlyInCreation 判斷對(duì)應(yīng)的單例對(duì)象是否在創(chuàng)建中,當(dāng)單例對(duì)象沒有被初始化完全(例如A定義的構(gòu)造函數(shù)依賴了B對(duì)象,得先去創(chuàng)建B對(duì)象,或者在populatebean過程中依賴了B對(duì)象,得先去創(chuàng)建B對(duì)象,此時(shí)A處于創(chuàng)建中)
- allowEarlyReference 是否允許從singletonFactories中通過getObject拿到對(duì)象
分析getSingleton的整個(gè)過程,Spring首先從singletonObjects(一級(jí)緩存)中嘗試獲取,如果獲取不到并且對(duì)象在創(chuàng)建中,則嘗試從earlySingletonObjects(二級(jí)緩存)中獲取,如果還是獲取不到并且允許從singletonFactories通過getObject獲取,則通過singletonFactory.getObject()(三級(jí)緩存)獲取。如果獲取到了則
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
則移除對(duì)應(yīng)的singletonFactory,將singletonObject放入到earlySingletonObjects,其實(shí)就是將三級(jí)緩存提升到二級(jí)緩存中!
Spring解決循環(huán)依賴的訣竅就在于singletonFactories這個(gè)cache,這個(gè)cache中存的是類型為ObjectFactory,其定義如下:
public interface ObjectFactory<T> {
T getObject() throws BeansException;}
在bean創(chuàng)建過程中,有兩處比較重要的匿名內(nèi)部類實(shí)現(xiàn)了該接口。一處是
new ObjectFactory<Object>() {
@Override public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
} }
在上文已經(jīng)提到,Spring利用其創(chuàng)建bean(這樣做真的很不明確呀...)
另一處就是:
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}});
此處就是解決循環(huán)依賴的關(guān)鍵,這段代碼發(fā)生在createBeanInstance之后,也就是說單例對(duì)象此時(shí)已經(jīng)被創(chuàng)建出來的。這個(gè)對(duì)象已經(jīng)被生產(chǎn)出來了,雖然還不完美(還沒有進(jìn)行初始化的第二步和第三步),但是已經(jīng)能被人認(rèn)出來了(根據(jù)對(duì)象引用能定位到堆中的對(duì)象),所以Spring此時(shí)將這個(gè)對(duì)象提前曝光出來讓大家認(rèn)識(shí),讓大家使用。
這樣做有什么好處呢?讓我們來分析一下“A的某個(gè)field或者setter依賴了B的實(shí)例對(duì)象,同時(shí)B的某個(gè)field或者setter依賴了A的實(shí)例對(duì)象”這種循環(huán)依賴的情況。A首先完成了初始化的第一步,并且將自己提前曝光到singletonFactories中,此時(shí)進(jìn)行初始化的第二步,發(fā)現(xiàn)自己依賴對(duì)象B,此時(shí)就嘗試去get(B),發(fā)現(xiàn)B還沒有被create,所以走create流程,B在初始化第一步的時(shí)候發(fā)現(xiàn)自己依賴了對(duì)象A,于是嘗試get(A),嘗試一級(jí)緩存singletonObjects(肯定沒有,因?yàn)锳還沒初始化完全),嘗試二級(jí)緩存earlySingletonObjects(也沒有),嘗試三級(jí)緩存singletonFactories,由于A通過ObjectFactory將自己提前曝光了,所以B能夠通過ObjectFactory.getObject拿到A對(duì)象(雖然A還沒有初始化完全,但是總比沒有好呀),B拿到A對(duì)象后順利完成了初始化階段1、2、3,完全初始化之后將自己放入到一級(jí)緩存singletonObjects中。此時(shí)返回A中,A此時(shí)能拿到B的對(duì)象順利完成自己的初始化階段2、3,最終A也完成了初始化,長大成人,進(jìn)去了一級(jí)緩存singletonObjects中,而且更加幸運(yùn)的是,由于B拿到了A的對(duì)象引用,所以B現(xiàn)在hold住的A對(duì)象也蛻變完美了!一切都是這么神奇?。?/p>
知道了這個(gè)原理時(shí)候,肯定就知道為啥Spring不能解決“A的構(gòu)造方法中依賴了B的實(shí)例對(duì)象,同時(shí)B的構(gòu)造方法中依賴了A的實(shí)例對(duì)象”這類問題了!
總結(jié)
Spring通過三級(jí)緩存加上“提前曝光”機(jī)制,配合Java的對(duì)象引用原理,比較完美地解決了某些情況下的循環(huán)依賴問題!