1. 程式人生 > >Spring 循環引用(二)源碼分析

Spring 循環引用(二)源碼分析

string contains tab 成功 關系 表示 href catch mov

Spring 循環引用(二)源碼分析

Spring 系列目錄(https://www.cnblogs.com/binarylei/p/10117436.html)

Spring 循環引用相關文章:

  1. 《Spring 循環引用(一)一個循環依賴引發的 BUG》:https://www.cnblogs.com/binarylei/p/10325698.html
  2. 《Spring 循環引用(二)源碼分析》:https://www.cnblogs.com/binarylei/p/10326046.html

一、Spring 中單例 bean 的管理

Spring 對單例 bean 的管理都是在 DefaultSingletonBeanRegistry 中完成的,這裏會涉及到其內部所使用的幾個內部屬性:

// 1.1 保存最終創建成功的單例 beanName -> beanInstance
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 1.2 中間變量,beanName -> Objectfactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 1.3 中間變量,bean 還在創建的時候就可以獲取,用於檢測循環引用 
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

這裏涉及用於存儲 bean 的不同的 Map,可能讓人感到崩潰,簡單解釋如下:

  1. singletonObjects:用於保存 beanName 和創建 bean 實例之間的關系。beanName -> beanInstance
  2. singletonFactories:用於保存 beanName 和對象工廠的引用關系,一旦最終對象被創建(通過 objectFactory.getObject()),此引用信息將刪除。beanName -> Objectfactory
  3. earlySingletonObjects:用於保存 beanName 和創建的原始 bean 的引用關系,註意這裏是原始 bean,即使用工廠方法或構造方法創建出來的對象,一旦對象最終創建好,此引用信息將刪除。 與 singletonObjects 的不同之處在於,此時 bean 還在創建過程中,而且之後還可以進行增強,也就是代理後這兩個 bean 就不是同一個了。可以通過 getBean 方法獲取到了,其目的是用來檢測循環引用。

從上面的解釋,可以看出,這 singletonFactories 和 earlySingletonObjects 都是一個臨時的輔助狀態。在所有的對象創建完畢之後,此兩個對象的 size 都為 0。那麽再來看下這兩個對象如何進行協作:

(1) 方法1:創建單例對象

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 1. 將這個 bean 添加到 singletonsCurrentlyInCreation 集合中,這樣就可以判斷 bean 是否存在創建
            beforeSingletonCreation(beanName);
            // 2. 初始化 bean,委托給 ObjectFactory 完成 
            singletonObject = singletonFactory.getObject();           
            // 3. 從 singletonsCurrentlyInCreation 移除該 bean
            afterSingletonCreation(beanName);       
            // 4. 創建完成進行註冊,這樣下次就可以從緩存中直接獲取這個對象了
            addSingleton(beanName, singletonObject);
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

(2) 方法2:單例對象創建完成進行註冊

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

(3) 方法3:將實例化後的對象暴露到容器中

Spring 在 bean 實例化後就會調用 addSingletonFactory 將這個對象提前暴露到容器中,這們就可以通過 getBean(A) 得到這個對象,即使這個對象仍正在創建。用於解決循環依賴。

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

(4) 方法4:從緩存中獲取 bean

這個方法也是專門用於解決循環依賴的問題,當不存在循環依賴時 earlySingletonObjects 總是 null。

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

二、Spring 創建 bean 過程

我們從 BeanFactory#getBean(beanName) 調用說起,看一下這幾個方法的調用順序:

2.1 AbstractBeanFactory#doGetBean

這個方法先從緩存中獲取 bean,沒有再創建 bean,因此會調用方法 4 和方法 1,我們看一下調用過程。

(1) getSingleton(beanName, true)

doGetBean 首先從緩存中獲取數據,Object sharedInstance = getSingleton(beanName),這個方法最終會調用 getSingleton(beanName, true)

(2) getSingleton(beanName, singletonFactory)

如果緩存中沒有 bean,則會調用 addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) 來創建一個新 bean,代碼如下:

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
        try {
            return createBean(beanName, mbd, args);
        }
        catch (BeansException ex) {
            destroySingleton(beanName);
            throw ex;
        }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}

一旦調用 getSingleton(beanName, singletonFactory) 方法,這個方法創建開始時就會標記這個 bean 為正在創建,創建結束後移除對應的標記。直接創建 bean 的過程實際上是委托給了 createBean 方法。繼續跟蹤這個方法。

2.2 AbstractAutowireCapableBeanFactory#doCreateBean

doCreateBean 方法中完成了單例的 bean 有以下幾個主要的步驟:

  1. createBeanInstance 實例化 bean 對象,一般是通過反射調用默認的構造器。
  2. populateBean bean 屬性註入,在這個步驟會從 Spring 容器中查找對應屬性字段的值,解決循環依賴問題。
  3. initializeBean 調用的 bean 定義的初始化方法。

(3) addSingletonFactory(beanName, singletonFactory)

在 createBeanInstance 後 populateBean 前 Spring 會將這個實例化的 bean 提前暴露到容器中,這樣 populateBean 屬性註入時就可以通過 getBean(A) 查找到。

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

(4) getSingleton(beanName, false)

在 bean 初始化完成還後還需要進行依賴的檢查,這時因為提前暴露的這個 bean(即使用工廠方法或構造方法創建出來的對象) initializeBean 還可以進行增強,這樣這兩個 bean 就不是同一個了。Spring 默認是不允許這種情況發生的。

if (earlySingletonExposure) {
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference;
        }
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
                if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                    actualDependentBeans.add(dependentBean);
                }
            }
            if (!actualDependentBeans.isEmpty()) {
                throw new BeanCurrentlyInCreationException(beanName,
                        "Bean with name ‘" + beanName + "‘ has been injected into other beans [" +
                        StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                        "] in its raw version as part of a circular reference, but has eventually been " +
                        "wrapped. This means that said other beans do not use the final version of the " +
                        "bean. This is often the result of over-eager type matching - consider using " +
                        "‘getBeanNamesOfType‘ with the ‘allowEagerInit‘ flag turned off, for example.");
            }
        }
    }
}

(5) addSingleton(beanName, singletonObject)

在 bean 創建結束後還有一步是在 getSingleton(beanName, singletonFactory) 中完成的,調用 addSingleton(beanName, singletonObject),即註冊最終的 bean,同時清空中間的輔助狀態。

這樣單例 bean 的創建過程就完成了,下面就需要分析循環引用下 singletonFactories、earlySingletonObjects 這兩個集合的狀態。

三、循環引用下 Bean 狀態分析

3.1 正常情況

在正常的情況下,調用順序如下:以下有無,表示是否持有對指定 Bean 的引用

過程 方法 singletonFactories earlySingletonObjects singletonObjects
緩存中獲取 getSingleton(beanName, true)
創建 bean getSingleton(beanName, singletonFactory)
提前暴露到容器中 addSingletonFactory(beanName, singletonFactory)
依賴檢查 getSingleton(beanName, false)
註冊 addSingleton(beanName, singletonObject)

可以看到正常情況下,單例 bean 暴露的對象只會出現在 singletonFactories 集合中,不可能出現在 earlySingletonObjects 集合中,除非在創建 bean 的過程中又調用了 getSingleton(beanName, true) 方法,也就是此時出現了循環引用。

3.2 循環引用

但是出現循環引用之後呢,就會出現這種情況:

過程 方法 singletonFactories earlySingletonObjects singletonObjects
緩存中獲取 A getSingleton(A, true) A無B無 A無B無 A無B無
創建 A getSingleton(A, singletonFactory) A無B無 A無B無 A無B無
暴露 A 到容器中 addSingletonFactory(A, singletonFactory) A有B無 A無B無 A無B無
populateBean(A, mbd, instanceWrapper) A 註入 B 時又依賴了 A,此時由 B 準備解析 A……
緩存中獲取 A getSingleton(A, true) A無B有 A有B無 A無B無
註冊 B addSingleton(B, singletonObject) A無B無 A有B無 A無B有
populateBean(A, mbd, instanceWrapper) 完成屬性註入
A- = initializeBean(beanName, exposedObject, mbd) 在 initializeBean 之後 A 變為 A-
依賴檢查 getSingleton(beanName, false) A無B無 A有B無 A無B有
註冊 A getSingleton(beanName, false) A無B無 A無B無 A有B有

在上面這個過程中,在對 A 進行驗證時,就會從 earlySingletonObjects 中取得一個 A,但是這個 A 和後面的 A- 可能不是同一個對象,這是因為有了 beanPostProcessor 存在,它可以改變 bean 的最終值,比如對原始 bean 進行封裝,代理等。在這個過程中,出現了 3 個對象 A, A-, B,而 B 中所持有的 A 對象為原始的 A。如果這裏的 A 和 A- 不是同一個對象,即產生了 beanA 有了 beanB 的引用,但 beanB 並沒有 beanA 的引用,而是另一個 beanA 的引用。


每天用心記錄一點點。內容也許不重要,但習慣很重要!

Spring 循環引用(二)源碼分析