Spring IOC 容器源碼分析 - 循環依賴的解決辦法-------轉載

原文鏈接:http://www.imooc.com/article/34150

 

1. 簡介

本文,我們來看一下 Spring 是如何解決循環依賴問題的。在本篇文章中,我會首先向大家介紹一下什麼是循環依賴。然後,進入源碼分析階段。爲了更好的說明 Spring 解決循環依賴的辦法,我將會從獲取 bean 的方法getBean(String)開始,把整個調用過程梳理一遍。梳理完後,再來詳細分析源碼。通過這幾步的講解,希望讓大家能夠弄懂什麼是循環依賴,以及如何解循環依賴。

循環依賴相關的源碼本身不是很複雜,不過這裏要先介紹大量的前置知識。不然這些源碼看起來很簡單,但讀起來可能卻也不知所云。那下面我們先來了解一下什麼是循環依賴。

2. 背景知識

2.1 什麼是循環依賴

所謂的循環依賴是指,A 依賴 B,B 又依賴 A,它們之間形成了循環依賴。或者是 A 依賴 B,B 依賴 C,C 又依賴 A。它們之間的依賴關係如下:

這裏以兩個類直接相互依賴爲例,他們的實現代碼可能如下:

public class BeanB {
    private BeanA beanA;
    // 省略 getter/setter
}

public class BeanA {
    private BeanB beanB;
}

配置信息如下:

<bean id="beanA" class="xyz.coolblog.BeanA">
    <property name="beanB" ref="beanB"/>
</bean>
<bean id="beanB" class="xyz.coolblog.BeanB">
    <property name="beanA" ref="beanA"/>
</bean>

IOC 容器在讀到上面的配置時,會按照順序,先去實例化 beanA。然後發現 beanA 依賴於 beanB,接在又去實例化 beanB。實例化 beanB 時,發現 beanB 又依賴於 beanA。如果容器不處理循環依賴的話,容器會無限執行上面的流程,直到內存溢出,程序崩潰。當然,Spring 是不會讓這種情況發生的。在容器再次發現 beanB 依賴於 beanA 時,容器會獲取 beanA 對象的一個早期的引用(early reference),並把這個早期引用注入到 beanB 中,讓 beanB 先完成實例化。beanB 完成實例化,beanA 就可以獲取到 beanB 的引用,beanA 隨之完成實例化。這裏大家可能不知道“早期引用”是什麼意思,這裏先彆着急,我會在下一章進行說明。

好了,本章先到這裏,我們繼續往下看。

2.2 一些緩存的介紹

在進行源碼分析前,我們先來看一組緩存的定義。如下:

/** 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);

根據緩存變量上面的註釋,大家應該能大致瞭解他們的用途。我這裏簡單說明一下吧:

緩存 用途
singletonObjects 用於存放完全初始化好的 bean,從該緩存中取出的 bean 可以直接使用
earlySingletonObjects 存放原始的 bean 對象(尚未填充屬性),用於解決循環依賴
singletonFactories 存放 bean 工廠對象,用於解決循環依賴

上一章提到了”早期引用“,所謂的”早期引用“是指向原始對象的引用。所謂的原始對象是指剛創建好的對象,但還未填充屬性。這樣講大家不知道大家聽明白了沒,不過沒聽明白也不要緊。簡單做個實驗就知道了,這裏我們先定義一個對象 Room:

/** Room 包含了一些電器 */
public class Room {
    private String television;
    private String airConditioner;
    private String refrigerator;
    private String washer;
    // 省略 getter/setter
}

配置如下:

<bean id="room" class="xyz.coolblog.demo.Room">
    <property name="television" value="Xiaomi"/>
    <property name="airConditioner" value="Gree"/>
    <property name="refrigerator" value="Haier"/>
    <property name="washer" value="Siemens"/>
</bean>

我們先看一下完全實例化好後的 bean 長什麼樣的。如下:

從調試信息中可以看得出,Room 的每個成員變量都被賦上值了。然後我們再來看一下“原始的 bean 對象”長的是什麼樣的,如下:

結果比較明顯了,所有字段都是 null。這裏的 bean 和上面的 bean 指向的是同一個對象Room@1567,但現在這個對象所有字段都是 null,我們把這種對象成爲原始的對象。形象點說,上面的 bean 對象是一個裝修好的房子,可以拎包入住了。而這裏的 bean 對象還是個毛坯房,還要裝修一下(填充屬性)纔行。

2.3 回顧獲取 bean 的過程

本節,我們來了解從 Spring IOC 容器中獲取 bean 實例的流程(簡化版),這對我們後續的源碼分析會有比較大的幫助。先看圖:

先來簡單介紹一下這張圖,這張圖是一個簡化後的流程圖。開始流程圖中只有一條執行路徑,在條件 sharedInstance != null 這裏出現了岔路,形成了綠色和紅色兩條路徑。在上圖中,讀取/添加緩存的方法我用藍色的框和標註了出來。至於虛線的箭頭,和虛線框裏的路徑,這個下面會說到。

我來按照上面的圖,分析一下整個流程的執行順序。這個流程從 getBean 方法開始,getBean 是個空殼方法,所有邏輯都在 doGetBean 方法中。doGetBean 首先會調用 getSingleton(beanName) 方法獲取 sharedInstance,sharedInstance 可能是完全實例化好的 bean,也可能是一個原始的 bean,當然也有可能是 null。如果不爲 null,則走綠色的那條路徑。再經 getObjectForBeanInstance 這一步處理後,綠色的這條執行路徑就結束了。

我們再來看一下紅色的那條執行路徑,也就是 sharedInstance = null 的情況。在第一次獲取某個 bean 的時候,緩存中是沒有記錄的,所以這個時候要走創建邏輯。上圖中的 getSingleton(beanName,
new ObjectFactory<Object>() {...}) 方法會創建一個 bean 實例,上圖虛線路徑指的是 getSingleton 方法內部調用的兩個方法,其邏輯如下:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    // 省略部分代碼
    singletonObject = singletonFactory.getObject();
    // ...
    addSingleton(beanName, singletonObject);
}

如上所示,getSingleton 會在內部先調用 getObject 方法創建 singletonObject,然後再調用 addSingleton 將 singletonObject 放入緩存中。getObject 在內部代用了 createBean 方法,createBean 方法基本上也屬於空殼方法,更多的邏輯是寫在 doCreateBean 方法中的。doCreateBean 方法中的邏輯很多,其首先調用了 createBeanInstance 方法創建了一個原始的 bean 對象,隨後調用 addSingletonFactory 方法向緩存中添加單例 bean 工廠,從該工廠可以獲取原始對象的引用,也就是所謂的“早期引用”。再之後,繼續調用 populateBean 方法向原始 bean 對象中填充屬性,並解析依賴。getObject 執行完成後,會返回完全實例化好的 bean。緊接着再調用 addSingleton 把完全實例化好的 bean 對象放入緩存中。到這裏,紅色執行路徑差不多也就要結束的。

我這裏沒有把 getObject、addSingleton 方法和 getSingleton(String, ObjectFactory) 並列畫在紅色的路徑裏,目的是想簡化一下方法的調用棧(都畫進來有點複雜)。我們可以進一步簡化上面的調用流程,比如下面:

這個流程看起來是不是簡單多了,命中緩存走綠色路徑,未命中走紅色的創建路徑。好了,本節先到這。

3. 源碼分析

好了,經過前面的鋪墊,現在我們終於可以深入源碼一探究竟了,想必大家已等不及了。那我不賣關子了,下面我們按照方法的調用順序,依次來看一下循環依賴相關的代碼。如下:

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

    // ...... 

    // 從緩存中獲取 bean 實例
    Object sharedInstance = getSingleton(beanName);

    // ......
}

public Object getSingleton(String beanName) {
    return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 從 singletonObjects 獲取實例,singletonObjects 中的實例都是準備好的 bean 實例,可以直接使用
    Object singletonObject = this.singletonObjects.get(beanName);
    // 判斷 beanName 對應的 bean 是否正在創建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 從 earlySingletonObjects 中獲取提前曝光的 bean
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 獲取相應的 bean 工廠
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 提前曝光 bean 實例(raw bean),用於解決循環依賴
                    singletonObject = singletonFactory.getObject();

                    // 將 singletonObject 放入緩存中,並將 singletonFactory 從緩存中移除
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

上面的源碼中,doGetBean 所調用的方法 getSingleton(String) 是一個空殼方法,其主要邏輯在 getSingleton(String, boolean) 中。該方法邏輯比較簡單,首先從 singletonObjects 緩存中獲取 bean 實例。若未命中,再去 earlySingletonObjects 緩存中獲取原始 bean 實例。如果仍未命中,則從 singletonFactory 緩存中獲取 ObjectFactory 對象,然後再調用 getObject 方法獲取原始 bean 實例的應用,也就是早期引用。獲取成功後,將該實例放入 earlySingletonObjects 緩存中,並將 ObjectFactory 對象從 singletonFactories 移除。看完這個方法,我們再來看看 getSingleton(String, ObjectFactory) 方法,這個方法也是在 doGetBean 中被調用的。這次我會把 doGetBean 的代碼多貼一點出來,如下:

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

    // ...... 
    Object bean;

    // 從緩存中獲取 bean 實例
    Object sharedInstance = getSingleton(beanName);

    // 這裏先忽略 args == null 這個條件
    if (sharedInstance != null && args == null) {
        // 進行後續的處理
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    } else {
        // ......

        // mbd.isSingleton() 用於判斷 bean 是否是單例模式
        if (mbd.isSingleton()) {
            // 再次獲取 bean 實例
            sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                @Override
                public Object getObject() throws BeansException {
                    try {
                        // 創建 bean 實例,createBean 返回的 bean 是完全實例化好的
                        return createBean(beanName, mbd, args);
                    } catch (BeansException ex) {
                        destroySingleton(beanName);
                        throw ex;
                    }
                }
            });
            // 進行後續的處理
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        }

        // ......
    }

    // ......

    // 返回 bean
    return (T) bean;
}

這裏的代碼邏輯和我在 2.3 回顧獲取 bean 的過程 一節的最後貼的主流程圖已經很接近了,對照那張圖和代碼中的註釋,大家應該可以理解 doGetBean 方法了。繼續往下看:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {

        // ......

        // 調用 getObject 方法創建 bean 實例
        singletonObject = singletonFactory.getObject();
        newSingleton = true;

        if (newSingleton) {
            // 添加 bean 到 singletonObjects 緩存中,並從其他集合中將 bean 相關記錄移除
            addSingleton(beanName, singletonObject);
        }

        // ......

        // 返回 singletonObject
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 將 <beanName, singletonObject> 映射存入 singletonObjects 中
        this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

        // 從其他緩存中移除 beanName 相關映射
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}

上面的代碼中包含兩步操作,第一步操作是調用 getObject 創建 bean 實例,第二步是調用 addSingleton 方法將創建好的 bean 放入緩存中。代碼邏輯並不複雜,相信大家都能看懂。那麼接下來我們繼續往下看,這次分析的是 doCreateBean 中的一些邏輯。如下:

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

    BeanWrapper instanceWrapper = null;

    // ......

    //  創建 bean 對象,並將 bean 對象包裹在 BeanWrapper 對象中返回
    instanceWrapper = createBeanInstance(beanName, mbd, args);

    // 從 BeanWrapper 對象中獲取 bean 對象,這裏的 bean 指向的是一個原始的對象
    final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);

    /*
     * earlySingletonExposure 用於表示是否”提前暴露“原始對象的引用,用於解決循環依賴。
     * 對於單例 bean,該變量一般爲 true。更詳細的解釋可以參考我之前的文章
     */ 
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        //  添加 bean 工廠對象到 singletonFactories 緩存中
        addSingletonFactory(beanName, new ObjectFactory<Object>() {
            @Override
            public Object getObject() throws BeansException {
                /* 
                 * 獲取原始對象的早期引用,在 getEarlyBeanReference 方法中,會執行 AOP 
                 * 相關邏輯。若 bean 未被 AOP 攔截,getEarlyBeanReference 原樣返回 
                 * bean,所以大家可以把 
                 *      return getEarlyBeanReference(beanName, mbd, bean) 
                 * 等價於:
                 *      return bean;
                 */
                return getEarlyBeanReference(beanName, mbd, bean);
            }
        });
    }

    Object exposedObject = bean;

    // ......

    //  填充屬性,解析依賴
    populateBean(beanName, mbd, instanceWrapper);

    // ......

    // 返回 bean 實例
    return exposedObject;
}

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 將 singletonFactory 添加到 singletonFactories 緩存中
            this.singletonFactories.put(beanName, singletonFactory);

            // 從其他緩存中移除相關記錄,即使沒有
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

上面的代碼簡化了不少,不過看起來仍有點複雜。好在,上面代碼的主線邏輯比較簡單,由三個方法組成。如下:

1. 創建原始 bean 實例  createBeanInstance(beanName, mbd, args)
2. 添加原始對象工廠對象到 singletonFactories 緩存中 
         addSingletonFactory(beanName, new ObjectFactory<Object>{...})
3. 填充屬性,解析依賴  populateBean(beanName, mbd, instanceWrapper)

到這裏,本節涉及到的源碼就分析完了。可是看完源碼後,我們似乎仍然不知道這些源碼是如何解決循環依賴問題的。難道本篇文章就到這裏了嗎?答案是否。下面我來解答這個問題,這裏我還是以 BeanA 和 BeanB 兩個類相互依賴爲例。在上面的方法調用中,有幾個關鍵的地方,下面一一列舉出來:

1. 創建原始 bean 對象

instanceWrapper = createBeanInstance(beanName, mbd, args);
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);

假設 beanA 先被創建,創建後的原始對象爲 BeanA@1234,上面代碼中的 bean 變量指向就是這個對象。

2. 暴露早期引用

addSingletonFactory(beanName, new ObjectFactory<Object>() {
    @Override
    public Object getObject() throws BeansException {
        return getEarlyBeanReference(beanName, mbd, bean);
    }
});

beanA 指向的原始對象創建好後,就開始把指向原始對象的引用通過 ObjectFactory 暴露出去。getEarlyBeanReference 方法的第三個參數 bean 指向的正是 createBeanInstance 方法創建出原始 bean 對象 BeanA@1234。

3. 解析依賴

populateBean(beanName, mbd, instanceWrapper);

populateBean 用於向 beanA 這個原始對象中填充屬性,當它檢測到 beanA 依賴於 beanB 時,會首先去實例化 beanB。beanB 在此方法處也會解析自己的依賴,當它檢測到 beanA 這個依賴,於是調用 BeanFactry.getBean("beanA") 這個方法,從容器中獲取 beanA。

4. 獲取早期引用

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) {
                    //  從 SingletonFactory 中獲取早期引用
                    singletonObject = singletonFactory.getObject();

                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

接着上面的步驟講,populateBean 調用 BeanFactry.getBean("beanA") 以獲取 beanB 的依賴。getBean("beanA") 會先調用 getSingleton("beanA"),嘗試從緩存中獲取 beanA。此時由於 beanA 還沒完全實例化好,於是 this.singletonObjects.get("beanA") 返回 null。接着 this.earlySingletonObjects.get("beanA") 也返回空,因爲 beanA 早期引用還沒放入到這個緩存中。最後調用 singletonFactory.getObject() 返回 singletonObject,此時 singletonObject != null。singletonObject 指向 BeanA@1234,也就是 createBeanInstance 創建的原始對象。此時 beanB 獲取到了這個原始對象的引用,beanB 就能順利完成實例化。beanB 完成實例化後,beanA 就能獲取到 beanB 所指向的實例,beanA 隨之也完成了實例化工作。由於 beanB.beanA 和 beanA 指向的是同一個對象 BeanA@1234,所以 beanB 中的 beanA 此時也處於可用狀態了。

以上的過程對應下面的流程圖:

4. 總結

到這裏,本篇文章差不多就快寫完了,不知道大家看懂了沒。這篇文章在前面做了大量的鋪墊,然後再進行源碼分析。相比於我之前寫的幾篇文章,本篇文章所對應的源碼難度上比之前簡單一些。但說實話也不好寫,我本來只想簡單介紹一下背景知識,然後直接進行源碼分析。但是又怕有的朋友看不懂,所以還是用了大篇幅介紹的背景知識。這樣寫,可能有的朋友覺得比較囉嗦。但是考慮到大家的水平不一,爲了保證讓大家能夠更好的理解,所以還是儘量寫的詳細一點。本篇文章總的來說寫的還是有點累的,花了一些心思思考怎麼安排章節順序,怎麼簡化代碼和畫圖。如果大家看完這篇文章,覺得還不錯的話,不妨給個贊吧,也算是對我的鼓勵吧。

由於個人的技術能力有限,若文章有錯誤不妥之處,歡迎大家指出來。好了,本篇文章到此結束,謝謝大家的閱讀。

參考:

附錄:Spring 源碼分析文章列表

注:文章更新時間爲該篇文章在我個人網站上的發佈時間,而非在慕課網上的發佈時間

Ⅰ. IOC

更新時間 標題
2018-05-30 Spring IOC 容器源碼分析系列文章導讀
2018-06-01 Spring IOC 容器源碼分析 - 獲取單例 bean
2018-06-04 Spring IOC 容器源碼分析 - 創建單例 bean 的過程
2018-06-06 Spring IOC 容器源碼分析 - 創建原始 bean 對象
2018-06-08 Spring IOC 容器源碼分析 - 循環依賴的解決辦法
2018-06-11 Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對象
2018-06-11 Spring IOC 容器源碼分析 - 餘下的初始化工作

Ⅱ. AOP

更新時間 標題
2018-06-17 Spring AOP 源碼分析系列文章導讀
2018-06-20 Spring AOP 源碼分析 - 篩選合適的通知器
2018-06-20 Spring AOP 源碼分析 - 創建代理對象
2018-06-22 Spring AOP 源碼分析 - 攔截器鏈的執行過程

Ⅲ. MVC

更新時間 標題
2018-06-29 Spring MVC 原理探祕 - 一個請求的旅行過程
2018-06-30 Spring MVC 原理探祕 - 容器的創建過程


作者:田小波
鏈接:http://www.imooc.com/article/34150
來源:慕課網
本文原創發佈於慕課網 ,轉載請註明出處,謝謝合作

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章