Spring解決循環依賴的方法

所謂Spring的循環依賴,指的是這樣一種場景:

當我們注入一個對象A時,需要注入對象A中標記了某些註解的屬性,這些屬性也就是對象A的依賴,把對象A中的依賴都初始化完成,對象A纔算是創建成功。那麼,如果對象A中有個屬性是對象B,而且對象B中有個屬性是對象A,那麼對象A和對象B就算是循環依賴,如果不加處理,就會出現:創建對象A-->處理A的依賴B-->創建對象B-->處理B的對象A-->創建對象A-->處理A的依賴B-->創建對象B......這樣無限的循環下去。

這事顯然不靠譜。

Spring處理循環依賴的基本思路是這樣的:

雖說要初始化一個Bean,必須要注入Bean裏的依賴,纔算初始化成功,但並不要求此時依賴的依賴也都注入成功,只要依賴對象的構造方法執行完了,這個依賴對象就算存在了,注入就算成功了,至於依賴的依賴,以後再初始化也來得及(參考Java的內存模型)。

因此,我們初始化一個Bean時,先調用Bean的構造方法,這個對象就在內存中存在了(對象裏面的依賴還沒有被注入),然後把這個對象保存下來,當循環依賴產生時,直接拿到之前保存的對象,於是循環依賴就被終止了,依賴注入也就順利完成了。

舉個例子:

假設對象A中有屬性是對象B,對象B中也有屬性是對象A,即A和B循環依賴。

  1. 創建對象A,調用A的構造,並把A保存下來。
  2. 然後準備注入對象A中的依賴,發現對象A依賴對象B,那麼開始創建對象B。
  3. 調用B的構造,並把B保存下來。
  4. 然後準備注入B的構造,發現B依賴對象A,對象A之前已經創建了,直接獲取A並把A注入B(注意此時的對象A還沒有完全注入成功,對象A中的對象B還沒有注入),於是B創建成功。
  5. 把創建成功的B注入A,於是A也創建成功了。

於是循環依賴就被解決了。

下面從Spring源碼的角度看一下,具體是個什麼邏輯。

在注入一個對象的過程中,調用了這樣一個方法:

Object sharedInstance = this.getSingleton(beanName);

這段代碼在AbstractBeanFactory類的doGetBean()方法中。

這裏得到的Object就是試圖是要創建的對象,beanName就是要創建的對象的類名,這裏getSingleton()方法的代碼如下:

@Nullable
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解決循環依賴的關鍵方法,在這個方法中,使用了三層列表來查詢的方式,這三層列表分別是:

singletonObjects

earlySingletonObjects

singletonFactories

這個方法中用到的幾個判斷邏輯,體現了Spring解決循環依賴的思路,不過實際上對象被放入這三層的順序是和方法查詢的循序相反的,也就是說,在循環依賴出現時,對象往往會先進入singletonFactories,然後earlySingletonObjects,然後singletonObjects。

下面看一下這個方法的代碼邏輯:

1,

Object singletonObject = this.singletonObjects.get(beanName);

方法首先從singletonObjects中獲取對象,當Spring準備新建一個對象時,singletonObjects列表中是沒有這個對象的,然後進入下一步。

2,

if (singletonObject == null && isSingletonCurrentlyInCreation(beanName))

除了判斷null之外,有一個isSingletonCurrentlyInCreation的判斷,實際上當Spring初始化了一個依賴注入的對象,但還沒注入對象屬性的時候,Spring會把這個bean加入singletonsCurrentlyInCreation這個set中,也就是把這個對象標記爲正在創建的狀態,這樣,如果Spring發現要創建的bean在singletonObjects中沒有,但在singletonsCurrentlyInCreation中有,基本上就可以認定爲循環依賴了(在創建bean的過程中發現又要創建這個bean,說明bean的某個依賴又依賴了這個bean,即循環依賴)。

舉個例子:對象A和對象B循環依賴,那麼初始化對象A之後(執行了構造方法),要把A放入singletonsCurrentlyInCreation,對象A依賴了對象B,那麼就要再初始化對象B,如果這個對象B又依賴了對象A,也就是形成了循環依賴,那麼當我們注入對象B中的屬性A時,進入這個代碼邏輯,就會發現,我們要注入的對象A已經在singletonsCurrentlyInCreation中了,後面的邏輯就該處理這種循環依賴了。

3,

singletonObject = this.earlySingletonObjects.get(beanName);

這裏引入了earlySingletonObjects列表,這是個爲了循環依賴而存在的列表,從名字就可以看到,是個預創建的對象列表,剛剛創建的對象在這個列表裏一般也沒有。

4,

if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);

earlySingletonObjects也沒有則從singletonFactories中獲取,前面說到singletonFactories是對象保存的第一步,實際上對象初始化後,可能還沒有注入對象的依賴,就把對象放入了這個列表。

如果是循環依賴,此時的singletonFactories中一般是會存在目標對象的,舉個例子:對象A和對象B循環依賴,那麼初始化了對象A(執行了構造方法),還沒有注入對象A的依賴時,就會把A放入singletonFactories,然後開始注入A的依賴,發現A依賴B,那麼需要構對象B,構造過程也是執行了B的構造後就把B放到singletonFactories,然後開始注入B的依賴,發現B依賴A,在第二步中提到,此時A已經在singletonsCurrentlyInCreation列表裏了,所以會進入此段代碼邏輯,而且此時時對象A在singletonFactories中確實存在,因爲這已經是第二次試圖創建對象A了。

5,

if (singletonFactory != null) {
    singletonObject = singletonFactory.getObject();
    this.earlySingletonObjects.put(beanName, singletonObject);
    this.singletonFactories.remove(beanName);
}

代碼到這裏基本已經確定我們要創建的這個對象已經發生循環依賴了,然後Spring進行了這樣的操作,把這個對象加入到earlySingletonObjects中,然後把該對象從singletonFactories中刪掉。

6,其實上面5步已經執行完了該方法的代碼,這裏加的第6步是爲了解釋循環依賴的結果。在這個方法的代碼之後,會把bean完整的進行初始化和依賴的注入,在完成了bean的初始化後,後面代碼邏輯中會調用一個這樣的方法:

getSingleton(String beanName, ObjectFactory<?> singletonFactory)

這個方法中有個小小的子方法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,把bean放入singletonObjects中,並把bean從earlySingletonObjects和singletonFactories中刪除,這個方法和上面分析的方法組成了Spring處理循環依賴的邏輯。

綜上,Spring處理循環依賴的流程大概就是以下這樣,假設對象A和對象B循環依賴:

步驟 操作 三層列表中的內容
1 開始初始化對象A

singletonFactories:

earlySingletonObjects:

singletonObjects:

2 調用A的構造,把A放入singletonFactories

singletonFactories:A

earlySingletonObjects:

singletonObjects:

3 開始注入A的依賴,發現A依賴對象B

singletonFactories:A

earlySingletonObjects:

singletonObjects:

4 開始初始化對象B

singletonFactories:A,B

earlySingletonObjects:

singletonObjects:

5 調用B的構造,把B放入singletonFactories

singletonFactories:A,B

earlySingletonObjects:

singletonObjects:

6 開始注入B的依賴,發現B依賴對象A

singletonFactories:A,B

earlySingletonObjects:

singletonObjects:

7

開始初始化對象A,發現A在singletonFactories裏有,則直接獲取A,

把A放入earlySingletonObjects,把A從singletonFactories刪除

singletonFactories:B

earlySingletonObjects:A

singletonObjects:

8 對象B的依賴注入完成

singletonFactories:B

earlySingletonObjects:A

singletonObjects:

9

對象B創建完成,把B放入singletonObjects,

把B從earlySingletonObjects和singletonFactories中刪除

singletonFactories:

earlySingletonObjects:A

singletonObjects:B

10 對象B注入給A,繼續注入A的其他依賴,直到A注入完成

singletonFactories:

earlySingletonObjects:A

singletonObjects:B

11

對象A創建完成,把A放入singletonObjects,

把A從earlySingletonObjects和singletonFactories中刪除

singletonFactories:

earlySingletonObjects: 

singletonObjects:A,B

12 循環依賴處理結束,A和B都初始化和注入完成

singletonFactories:

earlySingletonObjects:

singletonObjects:A,B

 

以上,希望我說清楚了。

另外,源碼分析部分截取自這篇文章:

https://blog.csdn.net/lkforce/article/details/95456154

這篇文章是SpingBoot啓動流程的源碼分析。

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