Spring面試題之循環(huán)依賴(lài)的理解

最近面試的時(shí)候發(fā)現(xiàn)很多人會(huì)問(wèn)Spring是如何解決循環(huán)依賴(lài)的,雖然知道是通過(guò)三級(jí)緩存去解決的,但是也僅僅只是知其然,不知其所以然,抱著學(xué)習(xí)的心態(tài)還是好好捋一捋:

  • 三級(jí)緩存是如何解決循環(huán)依賴(lài)的?
  • 為什么是三級(jí)緩存?二級(jí)緩存行不行?
  • 有什么好的方式可以避免構(gòu)建IOC的時(shí)候產(chǎn)生循環(huán)依賴(lài)?

循環(huán)依賴(lài)的場(chǎng)景

這個(gè)場(chǎng)景其實(shí)分為很多種:
簡(jiǎn)單一點(diǎn)場(chǎng)景: A -> B -> A
復(fù)雜一點(diǎn)的場(chǎng)景:

  • A 依賴(lài) B,C
  • B依賴(lài)A
  • C依賴(lài)A

在我們業(yè)務(wù)邏輯越來(lái)越復(fù)雜的時(shí)候,難免因?yàn)閷蛹?jí)過(guò)深導(dǎo)致這種場(chǎng)景出現(xiàn),但是在沒(méi)有運(yùn)行的時(shí)候發(fā)現(xiàn)不了。

另外Spring是能夠解決set屬性賦值的循環(huán)依賴(lài),但是構(gòu)造器注入的是會(huì)有問(wèn)題的,構(gòu)造器在實(shí)例化的時(shí)候會(huì)出現(xiàn)死結(jié),而set可以預(yù)先實(shí)例化后賦值所以好解決。

三級(jí)依賴(lài)是如何解決的?

首先我們要了解三級(jí)緩存的用處:

  • 一級(jí)緩存 singletonObjects : 用于保存實(shí)例化、注入、初始化完成的bean實(shí)例。

這里就是生命周期已經(jīng)加載完成了的對(duì)象

  • 二級(jí)緩存 earlySingletonObjects : 用于保存實(shí)例化完成的bean實(shí)例.

其實(shí)也就是new完了的對(duì)象,但是沒(méi)有進(jìn)行set(依賴(lài)注入)、以及初始化的對(duì)象,就是簡(jiǎn)單的實(shí)例化對(duì)象。

  • 三級(jí)緩存 singletonFactories : 用于保存bean創(chuàng)建ObjectFactory工廠(chǎng),方便后續(xù)可以創(chuàng)建代理對(duì)象。

這里很重要,這個(gè)工廠(chǎng)里面會(huì)包含bean的創(chuàng)建,可能是普通對(duì)象,可能是代理過(guò)后的對(duì)象??梢岳斫鉃橄劝裯ew完之后的實(shí)例引用先獲取到。

循環(huán)依賴(lài)場(chǎng)景: A -> B -> A

了解大概流程,先不糾結(jié)于細(xì)節(jié)。

image.png

緩存獲取順序的代碼:
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 先從一級(jí)緩存中獲取
  Object singletonObject = this.singletonObjects.get(beanName);
  if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
    synchronized(this.singletonObjects) {
        // 然后從二級(jí)緩存里面獲取對(duì)象
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            // 最后從三級(jí)緩存中獲取對(duì)象
            ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
                // 從工廠(chǎng)里面獲取對(duì)應(yīng)的值,可能是普通實(shí)例,可能是代理對(duì)象
                singletonObject = singletonFactory.getObject();
                // 放入二級(jí)緩存
                this.earlySingletonObjects.put(beanName, singletonObject);
                // 刪除三級(jí)緩存
                this.singletonFactories.remove(beanName);
            }
        }
    }
  }
    return singletonObject;
}

這里有個(gè)很重要的點(diǎn):就是當(dāng)B要獲取A的時(shí)候,從三級(jí)緩存里面查找,這時(shí)候已經(jīng)能夠找到了,就會(huì)從ObjectFactory工廠(chǎng)中返回一個(gè)對(duì)象,這個(gè)對(duì)象可能是普通實(shí)例也可能是代理對(duì)象。 這個(gè)時(shí)候會(huì)加入到二級(jí)緩存中,下一次查找就能從一級(jí),然后到二級(jí)直接找到對(duì)象了,不會(huì)在走到三級(jí)封裝成ObjectFactory對(duì)象了(每次從工廠(chǎng)里面拿可能會(huì)不是同一個(gè)實(shí)例)。

也就是說(shuō),發(fā)生循環(huán)的時(shí)候,會(huì)從工廠(chǎng)中將對(duì)象提前實(shí)例化出來(lái),然后這個(gè)引用會(huì)被會(huì)注入到發(fā)生循環(huán)依賴(lài)的Bean作為屬性填充。

另外下面的代碼是創(chuàng)建bean中,會(huì)預(yù)先將bean封裝成ObjectFactory對(duì)象的代碼

// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean
// addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {
// .. 省略
// 加入三級(jí)緩存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

Object exposedObject = bean;

try {
    // 然后開(kāi)始對(duì)該bean進(jìn)行屬性賦值
    populateBean(beanName, mbd, instanceWrapper);
    // 執(zhí)行init方法.初始化bean
    exposedObject = initializeBean(beanName, exposedObject, mbd);
}
catch (Throwable ex) {
}

return exposedObject;
// .. 省略
}
// 封裝之后,加入三級(jí)緩存.
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 先加入三級(jí)緩存
            this.singletonFactories.put(beanName, singletonFactory);
            // 刪除二級(jí)緩存
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}
// org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference
// 這里是ObjectFactory對(duì)象構(gòu)建并獲取的邏輯
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        // 這里會(huì)提前執(zhí)行后置處理器,有可能會(huì)返回的是一個(gè)代理對(duì)象
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            // 通過(guò)這個(gè)SmartInstantiationAwareBeanPostProcessor類(lèi)型的執(zhí)行器,來(lái)獲取提前暴露對(duì)象的邏輯
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

// 當(dāng)實(shí)例創(chuàng)建完成之后,會(huì)加入到單例工廠(chǎng),從二級(jí)緩存升級(jí)到一級(jí)緩存中
protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        this.singletonObjects.put(beanName, singletonObject);
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

這個(gè)時(shí)候B初始化好了直接加入到一級(jí)緩存中,而A在三級(jí)緩存被查找到后,放入二級(jí)緩存中,下一次查找會(huì)直接從二級(jí)緩存升級(jí)到一級(jí)緩存。流程結(jié)束。

我估計(jì)你現(xiàn)在還是會(huì)有點(diǎn)疑問(wèn):
ObjectFactory中獲取的對(duì)象確實(shí)是提前暴露實(shí)例化的對(duì)象,但是它是咋進(jìn)行屬性填充的?

還是看下圖吧:

引用邏輯

我不知道注釋的代碼你能否理解...

總的來(lái)說(shuō)就是引用邏輯,先將引用傳遞到工廠(chǎng)中,讓工廠(chǎng)構(gòu)建早期bean的時(shí)候是基于這個(gè)實(shí)例的引用去做的,這個(gè)時(shí)候依賴(lài)注入給其他Bean的時(shí)候也是基于該引用,所以等到該bean初始化完成,其他被其依賴(lài)注入的bean的引用就是初始化完成的。

為什么要三級(jí)緩存? 二級(jí)緩存行不行?

從上面的流程上來(lái)看二級(jí)緩存只是為了將工廠(chǎng)得到的實(shí)例對(duì)象預(yù)先存儲(chǔ)在二級(jí)緩存中,作用也不是特別明顯。

但是首先要思考一個(gè)問(wèn)題:假設(shè)干掉二級(jí)緩存,三級(jí)變成兩級(jí)。

假設(shè)循環(huán)依賴(lài)的場(chǎng)景是: A->B->C->B->A

流程圖

TestService1注入到TestService3又需要從第三級(jí)緩存中獲取實(shí)例,而第三級(jí)緩存里保存的并非真正的實(shí)例對(duì)象,而是ObjectFactory對(duì)象。
說(shuō)白了,兩次從三級(jí)緩存中獲取都是ObjectFactory對(duì)象,而通過(guò)它創(chuàng)建的實(shí)例對(duì)象每次可能都不一樣的,比如代理對(duì)象,每次獲取的都是一個(gè)新的代理對(duì)象。

為了解決這個(gè)問(wèn)題,spring引入的第二級(jí)緩存。上面圖1其實(shí)TestService1對(duì)象的實(shí)例已經(jīng)被添加到第二級(jí)緩存中了,而在TestService1注入到TestService3時(shí),只用從第二級(jí)緩存中獲取該對(duì)象即可。

所以二級(jí)緩存還是有必要的。區(qū)分工廠(chǎng)獲取的對(duì)象和具體實(shí)例的引用對(duì)象。

image.png

還有個(gè)問(wèn)題,第三級(jí)緩存中為什么要添加ObjectFactory對(duì)象,直接保存實(shí)例對(duì)象不行嗎?

答:不行,因?yàn)榧偃缒阆雽?duì)添加到三級(jí)緩存中的實(shí)例對(duì)象進(jìn)行增強(qiáng),直接用實(shí)例對(duì)象是行不通的。

直接反射實(shí)例化的話(huà),沒(méi)辦法經(jīng)過(guò)SpringBean的后置處理器參考getEarlyBeanReference方法生成增強(qiáng)對(duì)象。

有什么好的方式避免循環(huán)依賴(lài)嗎?

比如我明顯知道A 依賴(lài) B 了,這個(gè)時(shí)候B也需要A。

我們可以采用懶加載的方式以及容器先加載完的方式再獲取.

  1. @Layz注解,延遲加載
  2. B實(shí)現(xiàn)ApplicationContextAware的接口獲取到上下文,然后從上下文中獲取A,總的來(lái)說(shuō)也是懶加載的思路.

好了,以上僅僅是本人在遇到這個(gè)問(wèn)題的一些延伸及參考加思考所寫(xiě),感謝你這么忙還能觀(guān)看我的文章。

如果有問(wèn)題歡迎留言交流,我會(huì)及時(shí)回復(fù),希望共同進(jìn)步。

參考文章

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容