1. 程式人生 > >Spring-bean的迴圈依賴以及解決方式

Spring-bean的迴圈依賴以及解決方式

版權宣告:本文為博主原創文章,未經博主允許不得轉載。    https://blog.csdn.net/u010853261/article/details/77940767 本文主要是分析Spring bean的迴圈依賴,以及Spring的解決方式。 通過這種解決方式,我們可以應用在我們實際開發專案中。

什麼是迴圈依賴? 怎麼檢測迴圈依賴 Spring怎麼解決迴圈依賴 Spring對於迴圈依賴無法解決的場景 Spring解決迴圈依賴的方式我們能夠學到什麼? 1. 什麼是迴圈依賴? 迴圈依賴其實就是迴圈引用,也就是兩個或則兩個以上的bean互相持有對方,最終形成閉環。比如A依賴於B,B依賴於C,C又依賴於A。如下圖:

注意,這裡不是函式的迴圈呼叫,是物件的相互依賴關係。迴圈呼叫其實就是一個死迴圈,除非有終結條件。

Spring中迴圈依賴場景有:  (1)構造器的迴圈依賴  (2)field屬性的迴圈依賴。

2. 怎麼檢測是否存在迴圈依賴 檢測迴圈依賴相對比較容易,Bean在建立的時候可以給該Bean打標,如果遞迴呼叫回來發現正在建立中的話,即說明了迴圈依賴了。

3. Spring怎麼解決迴圈依賴 Spring的迴圈依賴的理論依據其實是基於Java的引用傳遞,當我們獲取到物件的引用時,物件的field或則屬性是可以延後設定的(但是構造器必須是在獲取引用之前)。

Spring的單例物件的初始化主要分為三步:    (1)createBeanInstance:例項化,其實也就是呼叫物件的構造方法例項化物件

(2)populateBean:填充屬性,這一步主要是多bean的依賴屬性進行填充

(3)initializeBean:呼叫spring xml中的init 方法。

從上面講述的單例bean初始化步驟我們可以知道,迴圈依賴主要發生在第一、第二部。也就是構造器迴圈依賴和field迴圈依賴。

那麼我們要解決迴圈引用也應該從初始化過程著手,對於單例來說,在Spring容器整個生命週期內,有且只有一個物件,所以很容易想到這個物件應該存在Cache中,Spring為了解決單例的迴圈依賴問題,使用了三級快取。

首先我們看原始碼,三級快取主要指:

/** 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); 這三級快取分別指:  singletonFactories : 單例物件工廠的cache  earlySingletonObjects :提前暴光的單例物件的Cache  singletonObjects:單例物件的cache

我們在建立bean的時候,首先想到的是從cache中獲取這個單例的bean,這個快取就是singletonObjects。主要呼叫方法就就是:

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 != NULL_OBJECT ? singletonObject : null); } 上面的程式碼需要解釋兩個引數:

isSingletonCurrentlyInCreation()判斷當前單例bean是否正在建立中,也就是沒有初始化完成(比如A的構造器依賴了B物件所以得先去建立B物件, 或則在A的populateBean過程中依賴了B物件,得先去建立B物件,這時的A就是處於建立中的狀態。) allowEarlyReference 是否允許從singletonFactories中通過getObject拿到物件 分析getSingleton()的整個過程,Spring首先從一級快取singletonObjects中獲取。如果獲取不到,並且物件正在建立中,就再從二級快取earlySingletonObjects中獲取。如果還是獲取不到且允許singletonFactories通過getObject()獲取,就從三級快取singletonFactory.getObject()(三級快取)獲取,如果獲取到了則:

this.earlySingletonObjects.put(beanName, singletonObject);                         this.singletonFactories.remove(beanName); 從singletonFactories中移除,並放入earlySingletonObjects中。其實也就是從三級快取移動到了二級快取。

從上面三級快取的分析,我們可以知道,Spring解決迴圈依賴的訣竅就在於singletonFactories這個三級cache。這個cache的型別是ObjectFactory,定義如下:

public interface ObjectFactory<T> {     T getObject() throws BeansException; } 這個介面在下面被引用

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);         }     } } 這裡就是解決迴圈依賴的關鍵,這段程式碼發生在createBeanInstance之後,也就是說單例物件此時已經被創建出來(呼叫了構造器)。這個物件已經被生產出來了,雖然還不完美(還沒有進行初始化的第二步和第三步),但是已經能被人認出來了(根據物件引用能定位到堆中的物件),所以Spring此時將這個物件提前曝光出來讓大家認識,讓大家使用。

這樣做有什麼好處呢?讓我們來分析一下“A的某個field或者setter依賴了B的例項物件,同時B的某個field或者setter依賴了A的例項物件”這種迴圈依賴的情況。A首先完成了初始化的第一步,並且將自己提前曝光到singletonFactories中,此時進行初始化的第二步,發現自己依賴物件B,此時就嘗試去get(B),發現B還沒有被create,所以走create流程,B在初始化第一步的時候發現自己依賴了物件A,於是嘗試get(A),嘗試一級快取singletonObjects(肯定沒有,因為A還沒初始化完全),嘗試二級快取earlySingletonObjects(也沒有),嘗試三級快取singletonFactories,由於A通過ObjectFactory將自己提前曝光了,所以B能夠通過ObjectFactory.getObject拿到A物件(雖然A還沒有初始化完全,但是總比沒有好呀),B拿到A物件後順利完成了初始化階段1、2、3,完全初始化之後將自己放入到一級快取singletonObjects中。此時返回A中,A此時能拿到B的物件順利完成自己的初始化階段2、3,最終A也完成了初始化,進去了一級快取singletonObjects中,而且更加幸運的是,由於B拿到了A的物件引用,所以B現在hold住的A物件完成了初始化。

知道了這個原理時候,肯定就知道為啥Spring不能解決“A的構造方法中依賴了B的例項物件,同時B的構造方法中依賴了A的例項物件”這類問題了!因為加入singletonFactories三級快取的前提是執行了構造器,所以構造器的迴圈依賴沒法解決。 ---------------------