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

[toc]

循環(huán)依賴

循環(huán)依賴就是N個(gè)類中循環(huán)嵌套引用,如果日常開發(fā)中我們用new對象的方式發(fā)生這種循環(huán)依賴的程序運(yùn)行一直循環(huán)直到內(nèi)存溢出報(bào)錯(cuò),下面說一下Spring是如何解決的:

首先循環(huán)依賴處理有三種情況:

  • 構(gòu)造器的循環(huán)依賴,這種依賴Spring處理不了,直接拋出BeanCurrentlyInCreationException異常
  • 單例模式下的setteer循環(huán)依賴:通過三級緩存處理循環(huán)依賴
  • 非單例循環(huán)依賴:無法處理,拋出BeanCurrentlyInCreateionException異常

Spring單例對象的初始化大略分為三步:

  • createBeanInstance:實(shí)例化,其實(shí)也就是調(diào)用對象的構(gòu)造方法實(shí)例化對象
  • populateBean:填充屬性,這一步主要是多bean的雨來屬性進(jìn)行填充
  • initializeBean:調(diào)用配置的init方法

從上面講述的單例Bean初始化步驟,可以知道,循環(huán)依賴主要發(fā)生在第一步、第二步,也就是構(gòu)造器循環(huán)依賴和field循環(huán)依賴。

Spring處理三種循環(huán)依賴

構(gòu)造器循環(huán)依賴

this.singletonsCurrentlyCreation.add(beanName)將當(dāng)前要?jiǎng)?chuàng)建的Bean記錄在緩存中,Spring容器將每一個(gè)正在創(chuàng)建的Bean標(biāo)識符放在當(dāng)前創(chuàng)建的Bean池中,bean標(biāo)志:在創(chuàng)建過程中,將保持在這個(gè)池中,因此創(chuàng)建Bean過程中發(fā)現(xiàn)自己已經(jīng)在創(chuàng)建當(dāng)前Bean池里,將拋出BeanCurrentlyIncreationException異常表示循環(huán)依賴,而對創(chuàng)建完畢的Bean將從當(dāng)前Bean池中清除掉。

單例模式下,setter循環(huán)依賴

Spring為了解決單例的循環(huán)依賴問題,使用了三級緩存:

//Cache of singleton objects: bean name ---> bean instance
private final Map<String,Object> singletonObjects=new ConcurrentHashMap(256);
//Cache of early singleton objects: bean name --->bean instance
private final Map<String,Object> earlySingletonObjects=new HashMap(16);
//Cache of singleton factories: bean name--->ObjectsFactory
private final Map<String,ObjectFactory<?>> singletonFacrories=new HashMap(16);

從字面的意思來說:

  • singletonObjects:指單例對象的Cache,完成初始化單例對象的Cache(一級緩存)
  • earlySingletonObjects:只提前曝光的單例對象的CCache,完成實(shí)例化但是尚未初始化的,提前曝光的單例對象的Cache(二級緩存)
  • singletonFactories:指單例對象工廠的Cache,進(jìn)入實(shí)例化階段的單例對象工廠的Cache(三級緩存)

以上三個(gè)Cache構(gòu)成了三級緩存,Spring就用這三級緩存解決了循環(huán)依賴的問題。

創(chuàng)建Bean的時(shí)候,會(huì)首先從cache中獲取這個(gè)Bean,這個(gè)緩存就是sigletonObjects。
主要調(diào)用方法是:DefaultSingletonBeanRegistry類下面的getSingleton()方法:

/**
* 方法作用 返回返回以給定名稱注冊的(原始)單例對象。
* 描述:檢查已經(jīng)實(shí)例化的單例,還允許對當(dāng)前創(chuàng)建的單例的早期引用(解析循環(huán)引用)
* beanName:要?jiǎng)?chuàng)建的Bean的名稱
* allowEarlyReference:是否應(yīng)創(chuàng)建早期參考
* return 注冊的單例對象;如果找不到,則為null
*/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    //isSingletonCurrentlyInCreation,判斷當(dāng)前單例的Bean是否正在創(chuàng)建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            //allowEarlyReference,是否允許從singletonFactories中通過getObject拿到對象
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        singletonObject = singletonFactory.getObject();
                        //從singletonFactories中移除,并放入earlySingletonObjects中
                        //其實(shí)就是從三級緩存移動(dòng)到二級緩存
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                     }
             }
          }
    }
    return singletonObject;
}

從上面緩存的分析,可以知道,Spring解決循環(huán)依賴的訣竅就在于singletonFactories這個(gè)三級Cache,這個(gè)Cache的類型是ObjectFactory,定義如下:

public interface ObjectFactory<T> {

    /**
     * 返回一個(gè)實(shí)例可能是共享的或獨(dú)立的,由該工廠管理的對象
     * @return 結(jié)果的實(shí)例
     * @throws BeansException 創(chuàng)建錯(cuò)誤拋出異常
     */
    T getObject() throws BeansException;
}

這個(gè)接口在AbstractAutowireCapableBeanFactory里的實(shí)現(xiàn),并在核心方法doCreateBean()引用了下面方法:

/**
     *如果有必要添加用于生成指定單例的給定單例工廠
     * 要求對單例登記,例如能夠解決循環(huán)引用
     * @param beanName bean的名字
     * @param singletonFactory 單例對象的工廠
 */
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);
            }
        }
}

這段代碼發(fā)生在createBeanInstance之后,populatBean()之前,也就是單例對象此時(shí)已經(jīng)被創(chuàng)建處理(調(diào)用了構(gòu)造器),這個(gè)對象已經(jīng)被生產(chǎn)出來了,此時(shí)將對象提前曝光出來,讓大家使用。

這樣的好處A的某個(gè)field或者setter依賴了B的實(shí)例對象,同時(shí)B的某個(gè)filed或者seteer依賴了A的實(shí)例對象,這種循環(huán)依賴的情況,A首先先完成初始化的第一步,并且將自己提前曝光到singletonFactories中,此時(shí)進(jìn)行初始化第二步,發(fā)現(xiàn)自己依賴對象B,此時(shí)將嘗試get(B),發(fā)現(xiàn)B沒有被創(chuàng)建,所以走create流程,B在初始化第一步的時(shí)候發(fā)現(xiàn)自己依賴了對象A,于是嘗試get(A),嘗試一級緩存singletonObjects(肯定沒用,因?yàn)锳還沒有初始化完全),嘗試調(diào)用二級緩存earltSingletonObjects(也沒有),嘗試三級緩存singletonFactories,由于A通過ObjectsFactory將自己提前曝光了,所以B拿到A對象后順利完成了初始化階段的1,2,3初始化之后將自己放到已經(jīng)緩存中singletonObjects中,此時(shí)返回A中,A此時(shí)拿到了B的對象順利完成自己的初始化節(jié)點(diǎn)2,3最終A也完成了初始化,放進(jìn)了以及緩存singletonObjects中,而且更加幸運(yùn)的是,由于B拿到了A對象引用,所以B現(xiàn)在持有A對象完成了初始化。

非單例循環(huán)依賴

對于prototype作用域bean,Spring容器無法完成依賴注入,因?yàn)镾pring容器不進(jìn)行緩存prototype作用域的Bean,因此無法提前暴露一個(gè)創(chuàng)建中的Bean

總結(jié)

只有單例的Bean才能解決循環(huán)依賴問題。

首先完成創(chuàng)建Bean過程的第一步時(shí)候createBeanInstance,將已經(jīng)調(diào)用了構(gòu)造器但尚未進(jìn)行填充屬性的bean放入三級緩存中即singletonFactories,提前曝光出來,當(dāng)創(chuàng)建Bean進(jìn)行第二步時(shí)populateBean,填充屬性,當(dāng)把屬性填充成功時(shí)候,將從三級緩存中移除,放入二級緩存中,即earlySingletonObjects,當(dāng)進(jìn)行第三步initializeBean,Bean創(chuàng)建成功,將完全創(chuàng)建成功的Bean放入一級緩存中singletonObjects,Bean完成創(chuàng)建成功。

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

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