Spring IOC容器是如何解決循環(huán)依賴的問題互纯?

  • 什么是循環(huán)依賴?
    先看代碼:
public class A {
    private B b;
    // 省略set/get方法
}

public class B {
    private A a;
    // 省略set/get方法
}

可以看到A類里有一個(gè)屬性是B類對(duì)象迎变,而B類里也有一個(gè)屬性是A類對(duì)象充尉,則我們可以稱A類對(duì)象與B類對(duì)象之間互相循環(huán)依賴。然后我們對(duì)把這倆個(gè)類納入到IOC容器中進(jìn)行管理衣形,現(xiàn)在進(jìn)行xml配置:

<bean id="a" class="com.A">
    <property name="b" ref="b"/>
</bean>

<bean id="b" class="com.B">
    <property name="a" ref="a"/>
</bean>

當(dāng)配置好xml以后驼侠,我們創(chuàng)建容器,并且調(diào)用getBean方法來獲取某個(gè)對(duì)象泵喘,那么會(huì)發(fā)生什么事情呢泪电?正常邏輯應(yīng)該是發(fā)生了死循環(huán),a對(duì)象的創(chuàng)建需要依賴b對(duì)象纪铺,而b對(duì)象的創(chuàng)建同時(shí)也需要a對(duì)象相速。這簡直就是沒辦法解決嘛!但是SpringIOC卻解決了這個(gè)問題鲜锚,并且你可以正常的獲取到相應(yīng)的對(duì)象而不會(huì)發(fā)生錯(cuò)誤突诬。
那么SpringIOC是如何解決循環(huán)依賴的問題呢?

原理
SpringIOC解決循環(huán)依賴的思路就是依靠緩存芜繁,同時(shí)還得引出個(gè)概念即早期暴露引用旺隙。我們知道在IOC容器里bean的初始化的過程分為三個(gè)步驟:創(chuàng)建實(shí)例、屬性注入實(shí)例骏令、回調(diào)實(shí)例實(shí)現(xiàn)的接口方法蔬捷。解決思路就在這:當(dāng)我們創(chuàng)建實(shí)例與屬性注入實(shí)例這倆個(gè)步驟之間的時(shí)候,我們引入緩存榔袋,將這些已經(jīng)創(chuàng)建好但是并沒有注入屬性的實(shí)例放到緩存里周拐,而這些放在緩存里但是沒有被注入屬性的實(shí)例對(duì)象,就是解決循環(huán)依賴的方法凰兑,打個(gè)比方:A對(duì)象的創(chuàng)建需要引用到B對(duì)象妥粟,而B對(duì)象的創(chuàng)建也需要A對(duì)象,而此時(shí)當(dāng)B對(duì)象創(chuàng)建的時(shí)候直接從緩存里引用A對(duì)象(雖然不是完全體A對(duì)象吏够,畢竟沒有賦值處理)勾给,當(dāng)B對(duì)象完成創(chuàng)建以后再被A對(duì)象引用進(jìn)去,則A對(duì)象也完成了創(chuàng)建锅知。
這就是SpringIOC解決bean直接循環(huán)依賴的思路當(dāng)然有一個(gè)小問題播急,IOC能夠解決的只能是屬性之間的循環(huán)依賴,如果有bean之間的構(gòu)造器相互依賴則就解決不了只能報(bào)錯(cuò)了喉镰。

  • 我們現(xiàn)在來看看Spring IOC的源碼

先看一下下面介紹源碼里的緩存的表:

源碼 級(jí)別 描述
singletonObjects 一級(jí)緩存 用于存放完全初始化好的 bean旅择,從該緩存中取出的 bean 可以直接使用
earlySingletonObjects 二級(jí)緩存 存放原始的 bean 對(duì)象(尚未填充屬性),用于解決循環(huán)依賴
singletonFactories 三級(jí)緩存 存放 bean 工廠對(duì)象侣姆,用于解決循環(huán)依賴

省略不必要的代碼

protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {

    
    Object bean;

    // 從緩存中取得bean的實(shí)例
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        //進(jìn)行后續(xù)處理生真,如果是正常的普通bean則返回普通的bean,如果是實(shí)現(xiàn)了FactoryBean接口的bean則返回的是getObject里的內(nèi)容
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    else {
        if (!typeCheckOnly) {
            markBeanAsCreated(beanName);
        }

        try {
            final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
            checkMergedBeanDefinition(mbd, beanName, args);

            // 解決依賴的問題捺宗,這個(gè)跟我們說的依賴是不一樣的.可以忽略
            // ......
            

            // 創(chuàng)建單例 bean
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                    @Override
                    public Object getObject() throws BeansException {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                        catch (BeansException ex) {
                            // 發(fā)生異常柱蟀,銷毀bean
                            destroySingleton(beanName);
                            throw ex;
                        }
                    }
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }
        }
        catch (BeansException ex) {
            cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
    }

    // Check if required type matches the type of the actual bean instance.
    // ......
    return (T) bean;
}

以上是doGetBean方法里的代碼,當(dāng)然我省略了跟本章無關(guān)的代碼蚜厉。
一步步來吧长已,先進(jìn)行初始化a對(duì)象的操作,然后發(fā)現(xiàn)調(diào)用的是createBean(String beanName, RootBeanDefinition mbd, Object[] args)方法昼牛,而真正起作用的是doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)方法术瓮。而在這個(gè)方法里面包含了三個(gè)重要的方法createBeanInstance、populateBean贰健、initializeBean胞四,看過之前系列文章的人都知道這三個(gè)方法分別代表:創(chuàng)建實(shí)例、屬性注入伶椿、方法回調(diào)辜伟,這是bean初始化的核心方法。當(dāng)然下面這段代碼是在createBeanInstance和populateBean中間的一段doCreateBean的代碼脊另。


boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    // ......
    addSingletonFactory(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
            return getEarlyBeanReference(beanName, mbd, bean);
        }
    });
}

這段代碼在spring源碼注釋里描述是用來解決循環(huán)依賴的問題的导狡。包含了一個(gè)匿名內(nèi)部類ObjectFactory<T>(普通的工廠類返回的是getObject方法返回的對(duì)象),用getEarlyBeanReference實(shí)現(xiàn)了getObject方法偎痛。同時(shí)還調(diào)用了addSingletonFactory方法旱捧。分別來看一下各自方法的實(shí)現(xiàn):

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                if (exposedObject == null) {
                    return null;
                }
            }
        }
    }
    return exposedObject;
}
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);
        }
    }
}

可以看到在addSingletonFactory方法中,會(huì)將beanName與singletonFactory形成kv關(guān)系put進(jìn)singletonFactories里面踩麦。并且將earlySingletonObjects里面的key值為beanName的kv進(jìn)行移除枚赡。
此時(shí)a對(duì)象的早期暴露引用已經(jīng)存在了singletonFactories三級(jí)緩存里面。此時(shí)a對(duì)象進(jìn)行populateBean方法進(jìn)行屬性注入靖榕,發(fā)現(xiàn)需要依賴b對(duì)象标锄,緊接著就是去初始化b對(duì)象。繼續(xù)重復(fù)上面的步驟到b對(duì)象進(jìn)行屬性注入這一步的時(shí)候(此時(shí)singletonFactories三級(jí)緩存里已經(jīng)有了a對(duì)象的提前暴露引用和b對(duì)象的提前暴露引用的工廠對(duì)象)茁计,發(fā)現(xiàn)需要依賴a對(duì)象料皇,此時(shí)去獲取a對(duì)象,看代碼:

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);

//繼續(xù)看這個(gè)方法
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);
}

先從singletonObjects一級(jí)緩存里取星压,如果沒有取到践剂,則從earlySingletonObjects二級(jí)緩存里取,如果還是沒取到娜膘,則從singletonFactories三級(jí)緩存里取逊脯,取到以后進(jìn)行g(shù)etObject方法返回早期暴露對(duì)象引用,同時(shí)放進(jìn)earlySingletonObjects二級(jí)緩存里竣贪,并且三級(jí)緩存里進(jìn)行刪除該kv军洼。
那么到此巩螃,a對(duì)象的早期暴露引用已經(jīng)被b對(duì)象獲取到了,并且在singletonFactories三級(jí)緩存里已經(jīng)沒有a對(duì)象的早期暴露引用的工廠對(duì)象了匕争,a對(duì)象的早期暴露引用存在了二級(jí)緩存earlySingletonObjects里面避乏,當(dāng)然singletonFactories三級(jí)緩存依然有b對(duì)象的早期暴露引用的工廠對(duì)象。

繼續(xù):b對(duì)象拿到了a對(duì)象的早期暴露引用甘桑,進(jìn)行完屬性注入以后拍皮,則返回一個(gè)b對(duì)象了同時(shí)調(diào)用方法getSingleton(String beanName, ObjectFactory<?> singletonFactory),看源碼:

//我已經(jīng)刪除了很多無關(guān)的代碼
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    // ......
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                // ......              
                beforeSingletonCreation(beanName);
                boolean newSingleton = false;
               // ... ...
               //... ...
                try {
                    singletonObject = singletonFactory.getObject();
                    newSingleton = true;
                }
                finally {
                    // ... ...
                }
                if (newSingleton) {
                    addSingleton(beanName, singletonObject);
                }
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

其實(shí)就是倆個(gè)方法:singletonObject = singletonFactory.getObject();和addSingleton(beanName, singletonObject);至此我們不需要說明第一個(gè)了跑杭,著重來看一下addSingleton方法铆帽。

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

ok,上面源碼已經(jīng)說明了此時(shí)singletonObjects一級(jí)緩存將要存入b對(duì)象德谅,而二級(jí)緩存earlySingletonObjects和三級(jí)緩存singletonFactories則把相關(guān)緩存的對(duì)象移除爹橱。至此b對(duì)象則只存在一級(jí)緩存singletonObjects里面了。
當(dāng)b對(duì)象完成了初始化以后女阀,a對(duì)象則進(jìn)行相關(guān)屬性的注入引入b的對(duì)象宅荤。完成實(shí)例化的同時(shí)a對(duì)象也會(huì)調(diào)用一次addSingleton方法,那么a對(duì)象完成以后浸策,也就只有一級(jí)緩存singletonObjects里面才有a對(duì)象冯键。

至此,屬性的循環(huán)依賴問題則完美的得到解決庸汗。

  • 文末
    感謝 【減肥是生命的主旋律】 的提問和回答
    有一個(gè)小問題惫确,為什么在解決循環(huán)依賴問題的時(shí)候,我們會(huì)用到三級(jí)緩存singletonFactories呢蚯舱?感覺二級(jí)緩存earlySingletonObjects就可以解決問題了呢改化?
    那么答案就在這里:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                if (exposedObject == null) {
                    return null;
                }
            }
        }
    }
    return exposedObject;
}

在將三級(jí)緩存放入二級(jí)緩存的時(shí)候,會(huì)判斷是否有SmartInstantiationAwareBeanPostProcessor這樣的后置處理器枉昏,換句話說這里是給用戶提供接口擴(kuò)展的陈肛,所以采用了三級(jí)緩存。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末兄裂,一起剝皮案震驚了整個(gè)濱河市句旱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晰奖,老刑警劉巖谈撒,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匾南,居然都是意外死亡啃匿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溯乒,“玉大人夹厌,你說我怎么就攤上這事〕仁” “怎么了尊流?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵帅戒,是天一觀的道長灯帮。 經(jīng)常有香客問我,道長逻住,這世上最難降的妖魔是什么钟哥? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮瞎访,結(jié)果婚禮上腻贰,老公的妹妹穿的比我還像新娘。我一直安慰自己扒秸,他們只是感情好播演,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著伴奥,像睡著了一般写烤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拾徙,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天洲炊,我揣著相機(jī)與錄音,去河邊找鬼尼啡。 笑死暂衡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的崖瞭。 我是一名探鬼主播狂巢,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼书聚!你這毒婦竟也來了唧领?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤寺惫,失蹤者是張志新(化名)和其女友劉穎疹吃,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體西雀,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡萨驶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艇肴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腔呜。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叁温,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出核畴,到底是詐尸還是另有隱情膝但,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布谤草,位于F島的核電站跟束,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏丑孩。R本人自食惡果不足惜冀宴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望温学。 院中可真熱鬧略贮,春花似錦、人聲如沸仗岖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轧拄。三九已至揽祥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間紧帕,已是汗流浹背盔然。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留是嗜,地道東北人愈案。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像鹅搪,于是被迫代替她去往敵國和親站绪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容