spring5 源碼深度解析-----IOC 之 循環依賴處理


什么是循環依賴

循環依賴其實就是循環引用,也就是兩個或則兩個以上的bean互相持有對方,最終形成閉環。比如A依賴於B,B依賴於C,C又依賴於A。如下圖所示:

注意,這里不是函數的循環調用,是對象的相互依賴關系。循環調用其實就是一個死循環,除非有終結條件。
Spring中循環依賴場景有:
(1)構造器的循環依賴
(2)field屬性的循環依賴。 

對於構造器的循環依賴,Spring 是無法解決的,只能拋出 BeanCurrentlyInCreationException 異常表示循環依賴,所以下面我們分析的都是基於 field 屬性的循環依賴。

Spring 只解決 scope 為 singleton 的循環依賴,對於scope 為 prototype 的 bean Spring 無法解決,直接拋出 BeanCurrentlyInCreationException 異常。

如何檢測循環依賴

檢測循環依賴相對比較容易,Bean在創建的時候可以給該Bean打標,如果遞歸調用回來發現正在創建中的話,即說明了循環依賴了。

解決循環依賴

我們先從加載 bean 最初始的方法 doGetBean() 開始。

在 doGetBean() 中,首先會根據 beanName 從單例 bean 緩存中獲取,如果不為空則直接返回。

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;
}

這個方法主要是從三個緩存中獲取,分別是:singletonObjects、earlySingletonObjects、singletonFactories,三者定義如下:

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

這三級緩存分別指:
(1)singletonFactories : 單例對象工廠的cache
(2)earlySingletonObjects :提前暴光的單例對象的Cache
(3)singletonObjects:單例對象的cache

他們就是 Spring 解決 singleton bean 的關鍵因素所在,我稱他們為三級緩存,第一級為 singletonObjects,第二級為 earlySingletonObjects,第三級為 singletonFactories。這里我們可以通過 getSingleton() 看到他們是如何配合的,這分析該方法之前,提下其中的 isSingletonCurrentlyInCreation() 和 allowEarlyReference

  • isSingletonCurrentlyInCreation():判斷當前 singleton bean 是否處於創建中。bean 處於創建中也就是說 bean 在初始化但是沒有完成初始化,有一個這樣的過程其實和 Spring 解決 bean 循環依賴的理念相輔相成,因為 Spring 解決 singleton bean 的核心就在於提前曝光 bean。
  • allowEarlyReference:從字面意思上面理解就是允許提前拿到引用。其實真正的意思是是否允許從 singletonFactories 緩存中通過 getObject() 拿到對象,為什么會有這樣一個字段呢?原因就在於 singletonFactories 才是 Spring 解決 singleton bean 的訣竅所在,這個我們后續分析。

getSingleton() 整個過程如下:首先從一級緩存 singletonObjects 獲取,如果沒有且當前指定的 beanName 正在創建,就再從二級緩存中 earlySingletonObjects 獲取,如果還是沒有獲取到且運行 singletonFactories 通過 getObject() 獲取,則從三級緩存 singletonFactories 獲取,如果獲取到則,通過其 getObject() 獲取對象,並將其加入到二級緩存 earlySingletonObjects 中 從三級緩存 singletonFactories 刪除,如下:

singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);

這樣就從三級緩存升級到二級緩存了。

上面是從緩存中獲取,但是緩存中的數據從哪里添加進來的呢?一直往下跟會發現在 doCreateBean() ( AbstractAutowireCapableBeanFactory ) 中,有這么一段代碼:

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, () -> getEarlyBeanReference(beanName, mbd, bean));
}

也就是我們上一篇文章中講的最后一部分,提前將創建好但還未進行屬性賦值的的Bean放入緩存中。

如果 earlySingletonExposure == true 的話,則調用 addSingletonFactory() 將他們添加到緩存中,但是一個 bean 要具備如下條件才會添加至緩存中:

  • 單例
  • 運行提前暴露 bean
  • 當前 bean 正在創建中

addSingletonFactory() 代碼如下:

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);
        }
    }
}

從這段代碼我們可以看出 singletonFactories 這個三級緩存才是解決 Spring Bean 循環依賴的訣竅所在。同時這段代碼發生在 createBeanInstance() 方法之后,也就是說這個 bean 其實已經被創建出來了,但是它還不是很完美(沒有進行屬性填充和初始化),但是對於其他依賴它的對象而言已經足夠了(可以根據對象引用定位到堆中對象),能夠被認出來了,所以 Spring 在這個時候選擇將該對象提前曝光出來讓大家認識認識。

介紹到這里我們發現三級緩存 singletonFactories 和 二級緩存 earlySingletonObjects 中的值都有出處了,那一級緩存在哪里設置的呢?在類 DefaultSingletonBeanRegistry 中可以發現這個 addSingleton() 方法,源碼如下:

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);
    }
}

添加至一級緩存,同時從二級、三級緩存中刪除。這個方法在我們創建 bean 的鏈路中有哪個地方引用呢?其實在前面博客 LZ 已經提到過了,在 doGetBean() 處理不同 scope 時,如果是 singleton,則調用 getSingleton(),如下:

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {
            // Explicitly remove instance from singleton cache: It might have been put there
            // eagerly by the creation process, to allow for circular reference resolution.
            // Also remove any beans that received a temporary reference to the bean.
            destroySingleton(beanName);
            throw ex;
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            //....
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            //.....
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

至此,Spring 關於 singleton bean 循環依賴已經分析完畢了。所以我們基本上可以確定 Spring 解決循環依賴的方案了:Spring 在創建 bean 的時候並不是等它完全完成,而是在創建過程中將創建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 緩存中),這樣一旦下一個 bean 創建的時候需要依賴 bean ,則直接使用 ObjectFactory 的 getObject() 獲取了,也就是 getSingleton()中的代碼片段了。

到這里,關於 Spring 解決 bean 循環依賴就已經分析完畢了。最后來描述下就上面那個循環依賴 Spring 解決的過程:首先 A 完成初始化第一步並將自己提前曝光出來(通過 ObjectFactory 將自己提前曝光),在初始化的時候,發現自己依賴對象 B,此時就會去嘗試 get(B),這個時候發現 B 還沒有被創建出來,然后 B 就走創建流程,在 B 初始化的時候,同樣發現自己依賴 C,C 也沒有被創建出來,這個時候 C 又開始初始化進程,但是在初始化的過程中發現自己依賴 A,於是嘗試 get(A),這個時候由於 A 已經添加至緩存中(一般都是添加至三級緩存 singletonFactories ),通過 ObjectFactory 提前曝光,所以可以通過 ObjectFactory.getObject() 拿到 A 對象,C 拿到 A 對象后順利完成初始化,然后將自己添加到一級緩存中,回到 B ,B 也可以拿到 C 對象,完成初始化,A 可以順利拿到 B 完成初始化。到這里整個鏈路就已經完成了初始化過程了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM