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

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

Spring 系列目錄(http://www.javashuo.com/article/p-kqecupyl-bm.html)html

Spring 循環引用相關文章:java

  1. 《Spring 循環引用(一)一個循環依賴引起的 BUG》:http://www.javashuo.com/article/p-bwuysaxv-w.html
  2. 《Spring 循環引用(二)源碼分析》:http://www.javashuo.com/article/p-gqplicns-ce.html

1、Spring 中單例 bean 的管理

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

// 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。那麼再來看下這兩個對象如何進行協做:app

(1) 方法1:建立單例對象ide

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:將實例化後的對象暴露到容器中this

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:從緩存中獲取 beancode

這個方法也是專門用於解決循環依賴的問題,當不存在循環依賴時 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;
}

2、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 這兩個集合的狀態。

3、循環引用下 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 的引用。


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

相關文章
相關標籤/搜索