1. 程式人生 > >spring深入學習(二十二) IOC 之迴圈依賴處理

spring深入學習(二十二) IOC 之迴圈依賴處理

這篇分析 doCreateBean() 第三個過程:迴圈依賴處理。其實迴圈依賴並不僅僅只是在 doCreateBean() 中處理,其實在整個載入 bean 的過程中都有涉及,所以下篇內容並不僅僅只侷限於 doCreateBean(),而是從整個 Bean 的載入過程進行分析。

什麼是迴圈依賴

迴圈依賴其實就是迴圈引用,就是兩個或者兩個以上的 bean 互相引用對方,最終形成一個閉環,如 A 依賴 B,B 依賴 C,C 依賴 A,如下:

201808131001

迴圈依賴 其實就是一個死迴圈的過程,在初始化 A 的時候發現引用了 B,這時就會去初始化 B,然後又發現 B 引用 C,跑去初始化 C,初始化 C 的時候發現引用了 A,則又會去初始化 A,依次迴圈永不退出,除非有終結條件。

Spring 迴圈依賴的場景有兩種:

  1. 構造器的迴圈依賴
  2. field 屬性的迴圈依賴

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

在部落格 【死磕 Spring】—– IOC 之開啟 bean 的載入 中提到,Spring 只解決 scope 為 singleton 的迴圈依賴,對於scope 為 prototype 的 bean Spring 無法解決,直接丟擲 BeanCurrentlyInCreationException 異常。為什麼 Spring 不處理 prototype bean,其實如果理解 Spring 是如何解決 singleton bean 的迴圈依賴就明白了。這裡先賣一個關子,我們先來關注 Spring 是如何解決 singleton bean 的迴圈依賴的。

解決迴圈依賴

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

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

Object sharedInstance = getSingleton(beanName);

呼叫 getSingleton() 方法從單例快取中獲取,如下:

    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,三者定義如下:

    /** Cache of singleton objects: bean name --> bean instance */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    /** Cache of singleton factories: bean name --> ObjectFactory */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    /** Cache of early singleton objects: bean name --> bean instance */
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

意義如下:

  • singletonObjects:單例物件的cache
  • singletonFactories : 單例物件工廠的cache
  • earlySingletonObjects :提前暴光的單例物件的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));
}

如果 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(),如下:

前面幾篇部落格已經分析了 createBean(),這裡就不再闡述了,我們關注方法 getSingleton() 程式碼如下:

    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 完成初始化。到這裡整個鏈路就已經完成了初始化過程了。