Spring是如何解決迴圈依賴的

shkstart發表於2022-01-07

首先,需要明確的是spring對迴圈依賴的處理有三種情況:

①構造器的迴圈依賴:這種依賴spring是處理不了的,直 接丟擲BeanCurrentlylnCreationException異常。

②單例模式下的setter迴圈依賴:透過“三級快取”處理迴圈依賴。

③非單例迴圈依賴:無法處理。

spring單例物件的初始化大略分為三步:

  1. createBeanInstance:例項化,其實也就是呼叫物件的構造方法例項化物件
  2. populateBean:填充屬性,這一步主要是多bean的依賴屬性進行填充
  3. initializeBean:呼叫spring xml中的init 方法。

從上面講述的單例bean初始化步驟我們可以知道,迴圈依賴主要發生在第一、第二步。也就是構造器迴圈依賴和field迴圈依賴。 接下來,我們具體看看spring是如何處理三種迴圈依賴的。

1、構造器迴圈依賴

this .singletonsCurrentlylnCreation.add(beanName)將當前正要建立的bean 記錄在快取中 Spring 容器將每一個正在建立的bean 識別符號放在一個“當前建立bean 池”中, bean 標識 柏:在建立過程中將一直保持在這個池中,因此如果在建立bean 過程中發現自己已經在“當前 建立bean 池” 裡時,將丟擲BeanCurrentlylnCreationException 異常表示迴圈依賴;而對於建立 完畢的bean 將從“ 當前建立bean 池”中清除掉。

2、setter迴圈依賴

Spring為了解決單例的迴圈依賴問題,使用了三級快取。

/** Cache of singleton objects: bean name –> bean instance */private final Map singletonObjects = new ConcurrentHashMap(256);/** Cache of singleton factories: bean name –> ObjectFactory */private final Map> singletonFactories = new HashMap>(16);/** Cache of early singleton objects: bean name –> bean instance */private final Map earlySingletonObjects = new HashMap(16);複製程式碼

這三級快取的作用分別是:

singletonFactories : 進入例項化階段的單例物件工廠的cache (三級快取)

earlySingletonObjects :完成例項化但是尚未初始化的,提前暴光的單例物件的Cache (二級快取)

singletonObjects:完成初始化的單例物件的cache(一級快取)

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

protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);//isSingletonCurrentlyInCreation()判斷當前單例bean是否正在建立中if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);//allowEarlyReference 是否允許從singletonFactories中透過getObject拿到物件if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();//從singletonFactories中移除,並放入earlySingletonObjects中。//其實也就是從三級快取移動到了二級快取this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return (singletonObject != NULL_OBJECT ? singletonObject : null);}複製程式碼

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

public interface ObjectFactory<T> {T getObject() throws BeansException;}複製程式碼

這個介面在AbstractBeanFactory裡實現,並在核心方法doCreateBean()引用下面的方法:

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之後,populateBean()之前,也就是說單例物件此時已經被建立出來(呼叫了構造器)。這個物件已經被生產出來了,此時將這個物件提前曝光出來,讓大家使用。

這樣做有什麼好處呢?讓我們來分析一下“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物件完成了初始化。

3、非單例迴圈依賴

對於“prototype”作用域bean, Spring 容器無法完成依賴注入,因為Spring 容器不進行緩 存“prototype”作用域的bean ,因此無法提前暴露一個建立中的bean 。

版權宣告:本文為「尚矽谷」的原創文章,轉載請附上原文出處連結及本宣告。下載相關影片學習資料到官方網站。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/27721058/viewspace-2851121/,如需轉載,請註明出處,否則將追究法律責任。

相關文章