Spring IOC 源碼解析 (循環(huán)依賴的解決)

引言

之前的幾篇對(duì)Spring IOC源碼分析的文章,大體上把IOC容器內(nèi)部實(shí)現(xiàn)做了分析腥例,但在有些細(xì)節(jié)上并沒(méi)有很深入的去分析辅甥。本篇文章主要是分析Spring IOC容器對(duì)Bean之間的循環(huán)依賴是如何解決的

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

那么什么是循環(huán)依賴呢?簡(jiǎn)單的理解一下院崇,A依賴B肆氓,B又依賴A,這就構(gòu)成了一個(gè)最簡(jiǎn)單的循環(huán)依賴底瓣,為了幫助大家理解,新建兩個(gè)互相依賴的類(兒子和爸爸互相依賴沒(méi)有錯(cuò)吧 ..)

public class Father {
    private String name;
    private Son son;
}

public class Son {
    private String name;
    private Father father;
}

這兩個(gè)bean交給Spring管理

<bean id="son" class="com.wangjn.demo.impl.Son">
    <property name="name" value="son"></property>
    <property name="father" ref="father"></property>
</bean>

<bean id="father" class="com.wangjn.demo.impl.Father">
    <property name="name" value="father"></property>
    <property name="son" ref="son"></property>
</bean>

啟動(dòng)Spring IOC容器蕉陋,用getBean方法可以成功獲取son對(duì)象捐凭,并且也注入了father對(duì)象,可見Spring為我們解決了循環(huán)依賴的問(wèn)題凳鬓∽鲁Γ可是按照正常創(chuàng)建Bean的流程來(lái)說(shuō),這個(gè)過(guò)程將會(huì)是一個(gè)死循環(huán)缩举,因?yàn)樵趧?chuàng)建son對(duì)象為son注入father屬性時(shí)垦梆,就會(huì)去獲取father對(duì)象,而在獲取father對(duì)象賦值son屬性的時(shí)候仅孩,又會(huì)去獲取son對(duì)象托猩,從而就陷入了死循環(huán),然后程序崩潰辽慕。

可是結(jié)果并不是我們預(yù)料的那樣京腥,接下來(lái)就來(lái)分析Spring是如何解決這個(gè)問(wèn)題的

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

之前對(duì)IOC源碼分析的文章中有分析過(guò)Bean的創(chuàng)建過(guò)程,下面我將對(duì)循環(huán)依賴實(shí)現(xiàn)的某些細(xì)節(jié)作分析

Spring 用緩存解決循環(huán)依賴

讓我們回到AbstractBeanFactorydoGetBean方法溅蛉,doGetBean方法就是我們通過(guò)容器getBean方法實(shí)際調(diào)用的邏輯公浪,我們?cè)谶@里著重關(guān)注getSingleton方法他宛,之前的分析中有提到,調(diào)用getSingleton(beanName)方法的目的是為了從緩存中直接獲取已經(jīng)創(chuàng)建的Bean欠气,而不必重復(fù)去創(chuàng)建√鳎現(xiàn)在讓我們進(jìn)到getSingleton方法里面去看看它都做了啥,從哪個(gè)緩存取到了Bean對(duì)象

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

    final String beanName = transformedBeanName(name);
    Object bean;

    // 從緩存中獲取 bean
    Object sharedInstance = getSingleton(beanName);
    ... 省略其他創(chuàng)建bean的代碼
}

getSingleton方法

public Object getSingleton(String beanName) {
    // 默認(rèn)都是允許提前暴露對(duì)象
    return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {   
    // 從創(chuàng)建完成的bean緩存中獲取bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // 判斷該bean是否仍在創(chuàng)建中预柒,意思是Bean已經(jīng)完成實(shí)例化讯检,但還不完整。屬性還未完全注入
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 從提前暴露的Bean緩存容器(earlySingletonObjects)中獲取
            singletonObject = this.earlySingletonObjects.get(beanName);
            // 仍未獲取到則從singletonFactories緩存中獲取
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    // 加入到提前暴露Bean緩存(earlySingletonObjects)中
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 從singletonFactories緩存中移除
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    // 返回對(duì)象卫旱,這里返回的不一定是完全創(chuàng)建的對(duì)象
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

getSingleton方法中我們需要著重關(guān)注幾個(gè)Bean的緩存人灼,標(biāo)題已經(jīng)說(shuō)了,緩存是解決循環(huán)依賴的關(guān)鍵顾翼,下面我介紹一下上面代碼中提到了三種緩存

/** 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);
  1. singletonObjects 用于存放創(chuàng)建完成的單例對(duì)象
  2. singletonFactories 用于存放對(duì)象工廠類投放,這里是解決循環(huán)依賴的
  3. earlySingletonObjects 用于存放提前暴露的單例對(duì)象。指的是已經(jīng)完成Bean的實(shí)例化适贸,但還未完成屬性注入的不完整對(duì)象

再來(lái)說(shuō)上面代碼中取緩存的步驟灸芳,首先肯定是從singletonObjects中獲取完全創(chuàng)建完成的Bean對(duì)象,如果獲取不到拜姿,則從提前暴露對(duì)象緩存(earlySingletonObjects)中獲取烙样,還獲取不到再到singletonFactories中獲取

到這里為止,我們只分析了取Bean緩存的過(guò)程蕊肥,所以接下來(lái)我們要分析的就是放緩存的過(guò)程代碼

提前暴露Bean

現(xiàn)在讓我們?nèi)サ絼?chuàng)建Bean的過(guò)程谒获。如果緩存沒(méi)取到,會(huì)執(zhí)行創(chuàng)建Bean的邏輯壁却,找到AbstractAutowireCapableBeanFactory類的doCreateBean方法批狱,這個(gè)方法在之前的文章中有做過(guò)分析,但沒(méi)有對(duì)Bean緩存處理做分析展东。這里我們著重看中間解決循環(huán)依賴的那部分

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
        throws BeanCreationException {

    // Instantiate the bean.
    // 封裝bean的容器
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
        // 這里是創(chuàng)建 BeanWrapper 
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
    Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
    mbd.resolvedTargetType = beanType;

    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            try {
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            }
            catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Post-processing of merged bean definition failed", ex);
            }
            mbd.postProcessed = true;
        }
    }

    // 判斷是否需要提前暴露對(duì)象的引用赔硫,用于解決循環(huán)依賴
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isDebugEnabled()) {
            logger.debug("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
        }
        addSingletonFactory(beanName, new ObjectFactory<Object>() {
            @Override
            public Object getObject() throws BeansException {
                                 // 這里會(huì)與AOP相關(guān)
                return getEarlyBeanReference(beanName, mbd, bean);
            }
        });
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        // 依賴注入的主邏輯
        populateBean(beanName, mbd, instanceWrapper);
        if (exposedObject != null) {
            //  執(zhí)行一些初始化的方法
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
    }
    catch (Throwable ex) {
        if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
            throw (BeanCreationException) ex;
        }
        else {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
        }
    }

    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                if (!actualDependentBeans.isEmpty()) {
                    throw new BeanCurrentlyInCreationException(beanName,
                            "Bean with name '" + beanName + "' has been injected into other beans [" +
                            StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                            "] in its raw version as part of a circular reference, but has eventually been " +
                            "wrapped. This means that said other beans do not use the final version of the " +
                            "bean. This is often the result of over-eager type matching - consider using " +
                            "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }

    // Register bean as disposable.
    try {
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

    return exposedObject;
}

通過(guò)看代碼,可知在Bean完成實(shí)例化之后盐肃,注入屬性之前爪膊,Spring就將這個(gè)不完整的Bean放到了singletonFactories緩存中,從而讓這個(gè)Bean提前進(jìn)行了暴露砸王。這樣子在后續(xù)的屬性注入操作中推盛,如果存在循環(huán)依賴,就會(huì)從緩存中獲取到這個(gè)提前暴露的Bean处硬,從而可以順利完成依賴注入小槐。但是要注意這時(shí)候注入的對(duì)象是不完整的,但是因?yàn)橐蕾嚪揭呀?jīng)持有它的引用,所以后續(xù)對(duì)象的完整性是可以保證的

總結(jié)

本篇文章主要從Spring對(duì)Bean的緩存層面分析了其對(duì)循環(huán)依賴的解決凿跳,雖然是Spring幫我們解決了這個(gè)問(wèn)題件豌,但是對(duì)于實(shí)現(xiàn)的邏輯我們?nèi)匀粦?yīng)該去了解,譬如控嗜,通過(guò)查看源碼可知Spring僅僅對(duì)單例類型的循環(huán)依賴進(jìn)行解決茧彤,對(duì)于有狀態(tài)的BeanSpring并沒(méi)有去做處理,而是直接跑出異常疆栏,這些都是需要注意的曾掂。

博客原文地址戳這里

Spring 系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市壁顶,隨后出現(xiàn)的幾起案子珠洗,更是在濱河造成了極大的恐慌,老刑警劉巖若专,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件许蓖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡调衰,警方通過(guò)查閱死者的電腦和手機(jī)膊爪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)嚎莉,“玉大人米酬,你說(shuō)我怎么就攤上這事∏髀幔” “怎么了赃额?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)阁簸。 經(jīng)常有香客問(wèn)我爬早,道長(zhǎng),這世上最難降的妖魔是什么启妹? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮醉旦,結(jié)果婚禮上饶米,老公的妹妹穿的比我還像新娘。我一直安慰自己车胡,他們只是感情好檬输,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著匈棘,像睡著了一般丧慈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天逃默,我揣著相機(jī)與錄音鹃愤,去河邊找鬼。 笑死完域,一個(gè)胖子當(dāng)著我的面吹牛软吐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吟税,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凹耙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了肠仪?” 一聲冷哼從身側(cè)響起肖抱,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎异旧,沒(méi)想到半個(gè)月后意述,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡泽艘,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年欲险,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匹涮。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡天试,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出然低,到底是詐尸還是另有隱情喜每,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布雳攘,位于F島的核電站带兜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吨灭。R本人自食惡果不足惜刚照,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喧兄。 院中可真熱鬧无畔,春花似錦、人聲如沸吠冤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拯辙。三九已至郭变,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诉濒。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工周伦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人循诉。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓横辆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親茄猫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狈蚤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

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