Spring IOC源碼解析-循環依賴的解決方法

一.簡介

本文,我們將來看一下Spring是如何解決循環依賴問題的。在本篇文章中,首先會介紹下什麼是循環依賴,然後介紹下Spring將玄幻依賴分類的情況。最後,進行源碼解析。爲了更好的解析Spring解決循環依賴的辦法,將會從獲取bean的方法getBean開始,將這個調用過程梳理一遍,然後在詳細分析源碼。

二.什麼是循環依賴

2.1概述

循環依賴就是循環引用,就是兩個或多個bean之間的持有對方,如下圖所示:

循環依賴是無法解決的,除非有終結條件,否則就是死循環,最終導致內存溢出錯誤。

2.2 Spring對循環依賴分類

首先來定義循環引用類:

public class BeanA{
   
   private BeanB beanB;
   public void a(){
      beanB.b();
   }

   public BeanB getBeanB()
   {
       return beanB;
   }
    public void setBeanB()
        this.beanB=beanB;
   }
}

public class BeanB{
   
   private BeanA beanA;
   public void b(){
      beanA.a();
   }

   public Beana getBeana()
   {
       return beanA;
   }
    public void setBeanA()
        this.beanA=beanA;
   }
}

在Spring中將循環依賴的處理分爲3種情況。

2.2.1 構造器循環依賴

表示通過構造器注入構成的循環依賴,此依賴是無法解決的,只能拋出BeanCurrentlyInCreationException異常表示循環依賴。

如上面的循環類創建一樣。

<bean id="beanA" class="com.bean.BeanA">
     <comstructor-arg index="0" ref="beanB"/>
</bean>
<bean id="beanB" class="com.bean.BeanB">
     <comstructor-arg index="0" ref="beanA"/>
</bean>

Spring容器將每一個正在創建的bean標識符放在一個“當前創建bean池”中,bean標識符在創建過程中將一直保持在這個池中,因此如果在創建bean過程中發現自己已經在"當前創建池”中,將拋出BeanCurrentlyInCreationException異常表示循環依賴,而對於創建完畢的bean將從“當前創建bean池”中清除掉。

2.2.2 setter循環依賴

表示通過setter注入方式構成的循環依賴。對於setter注入造成的依賴是通過Spring容器提前暴露剛完成構造器注入但未完成其他步驟(如setter注入)的bean來完成的,而且只能解決單例作用域的bean循環依賴。通過提前暴露一個單例工廠方法,從而是其他bean能夠引用該bean。這個詳細講解在下面章節。

IOC 容器在讀到上面的配置時,會按照順序,先去實例化 testA。然後發現 testA依賴於 testB,接在又去實例化 testB。實例化testB的時候,發現testB又依賴於testC,testC又依賴testA。如果容器不處理循環依賴的話,容器會無限執行上面的流程,直到內存溢出,程序崩潰。當然,Spring 是不會讓這種情況發生的。在容器再次發現 testC依賴於 testA時,容器會獲取 testA對象的一個早期的引用(early reference),並把這個早期引用注入到 testC中,讓 testC先完成實例化。testC完成實例化,testB就可以獲取到 testC的引用,testB隨之完成實例化。testA就可以獲取到 testB的引用,testA隨之完成實例化。

2.2.3 prototype範圍的依賴處理

<bean id="beanA" class="com.bean.BeanA" scope="prototype">
   <property name="beanB" ref="beanB"/>
</bean>
<bean id="beanB" class="com.bean.BeanB" scope="prototype">
    <property name="beanA" ref="beanA"/>
</bean>

對於prototype作用域bean,Spring容器無法完成依賴注入,因爲Spring容器不進行緩存prototype作用域的bean,因此無法提前暴露一個創建中的bean。

三.Spring如何解決循環依賴(setter循環依賴)(這部分參考Spring IOC 容器源碼分析 - 循環依賴的解決辦法

3.1 一些緩存的介紹

/** 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 工廠對象,用於解決循環依賴

3.2 回顧獲取bean的過程

流程圖:

15284164871636.jpguploading.4e448015.gif正在上傳…重新上傳取消

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

我們再來看一下紅色的那條執行路徑,也就是 sharedInstance = null 的情況。在第一次獲取某個 bean 的時候,緩存中是沒有記錄的,所以這個時候要走創建邏輯。上圖中的 getSingleton(beanName,
new ObjectFactory() {…}) 方法會創建一個 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 對象放入緩存中。到這裏,紅色執行路徑差不多也就要結束的。

3.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 中被調用的。如下:

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

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)

上面的第二步就是暴露提前引用的地方,將bean工廠對象放到singletonFactories緩存中。

四.實驗解析

 

這裏我還是以 BeanA 和 BeanB 兩個類相互依賴爲例。在上面的方法調用中,有幾個關鍵的地方,下面一一列舉出來:

1. 創建原始 bean 對象

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

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

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

2.createBean中暴露早期引用

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 此時也處於可用狀態了。

一定要注意三個緩存的區別。

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

上面部分文章內容和圖片來自於Spring IOC 容器源碼分析 - 循環依賴的解決辦法

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