什麼是循環依賴?
其實就是在IOC容器初始化對象的時候,A對象的創建依賴B對象 A—>B ,B對象的創建又依賴A對象,B—>A,所以這樣子就產生了對象的循環依賴。
基於構造器的循環依賴
先上基於構造器的循環依賴例子:
@Component
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
public B(A a) {
this.a = a;
}
}
然後啓動項目測試:
不過基於構造器的循環依賴沒辦法解決,它是基於Bean的,沒有使用到緩存的提前暴露…
基於setter方法的循環依賴
還是先上例子:
@Component
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
啓動項目測試:
這裏項目啓動成功沒有報錯,setter已經注入成功了,下面我們來分析一下原因:
可以看到,這裏A和B中各自都以對方爲自己的全局屬性。這裏首先需要說明的一點,Spring實例化bean是通過ApplicationContext.getBean()方法來進行的。
如果要獲取的對象依賴了另一個對象,那麼其首先會創建當前對象,然後通過遞歸的調用ApplicationContext.getBean()方法來獲取所依賴的對象,最後將獲取到的對象注入到當前對象中。
這裏我們以上面的首先初始化A對象實例爲例進行講解。
首先Spring嘗試通過ApplicationContext.getBean()方法獲取A對象的實例,由於Spring容器中還沒有A對象實例,因而其會創建一個A對象
然後發現其依賴了B對象,因而會嘗試遞歸的通過ApplicationContext.getBean()方法獲取B對象的實例
但是Spring容器中此時也沒有B對象的實例,因而其還是會先創建一個B對象的實例。
這裏需要注意這個時間點,此時A對象和B對象都已經創建了,並且保存在Spring容器中了,只不過A對象的屬性b和B對象的屬性a都還沒有設置進去。
在前面Spring創建B對象之後,Spring發現B對象依賴了屬性A,因而還是會嘗試遞歸的調用ApplicationContext.getBean()方法獲取A對象的實例
因爲Spring中已經有一個A對象的實例,雖然只是半成品(其屬性b還未初始化),但其也還是目標bean,因而會將該A對象的實例返回。
此時,B對象的屬性a就設置進去了,然後還是ApplicationContext.getBean()方法遞歸的返回,也就是將B對象的實例返回,此時就會將該實例設置到A對象的屬性b中。
這個時候,注意A對象的屬性b和B對象的屬性a都已經設置了目標對象的實例了
這裏可能會比較疑惑的是,前面在爲對象B設置屬性a的時候,這個A類型屬性還是個半成品。但是需要注意的是,這個A是一個引用,其本質上還是最開始就實例化的A對象。
而在上面這個遞歸過程的最後,Spring將獲取到的B對象實例設置到了A對象的屬性b中了
這裏的A對象其實和前面設置到實例B中的半成品A對象是同一個對象,其引用地址是同一個,這裏爲A對象的b屬性設置了值,其實也就是爲那個半成品的a屬性設置了值。
下面我們通過一個流程圖來對這個過程進行講解:
圖中getBean()表示調用Spring的ApplicationContext.getBean()方法,而該方法中的參數,則表示我們要嘗試獲取的目標對象。
圖中的黑色箭頭表示一開始的方法調用走向,走到最後,返回了Spring中緩存的A對象之後,表示遞歸調用返回了,此時使用綠色的箭頭表示。
從圖中我們可以很清楚的看到,B對象的a屬性是在第三步中注入的半成品A對象,而A對象的b屬性是在第二步中注入的成品B對象,此時半成品的A對象也就變成了成品的A對象,因爲其屬性已經設置完成了
源碼解析
上面我們說到沒有設置屬性的半成品,這裏的標記工作Spring是使用ApplicationContext的屬性SetsingletonsCurrentlyInCreation來保存的,而半成品的A對象則是通過MapsingletonFactories來保存的
這裏的ObjectFactory是一個工廠對象,可通過調用其getObject()方法來獲取目標對象。在AbstractBeanFactory.doGetBean()方法中獲取對象的方法如下:
protected T doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 嘗試通過bean名稱獲取目標bean對象,比如這裏的A對象
Object sharedInstance = getSingleton(beanName);
// 我們這裏的目標對象都是單例的
if (mbd.isSingleton()) {
// 這裏就嘗試創建目標對象,第二個參數傳的就是一個ObjectFactory類型的對象,這裏是使用Java8的lamada
// 表達式書寫的,只要上面的getSingleton()方法返回值爲空,則會調用這裏的getSingleton()方法來創建
// 目標對象
sharedInstance = getSingleton(beanName, () -> {
try {
// 嘗試創建目標對象
return createBean(beanName, mbd, args);
} catch (BeansException ex) {
throw ex;
}
});
}
return (T) bean;
}
這裏的doGetBean()方法是非常關鍵的一個方法(中間省略了其他代碼),上面也主要有兩個步驟
第一個步驟的getSingleton()方法的作用是嘗試從緩存中獲取目標對象,如果沒有獲取到,則嘗試獲取半成品的目標對象;如果第一個步驟沒有獲取到目標對象的實例,那麼就進入第二個步驟
第二個步驟的getSingleton()方法的作用是嘗試創建目標對象,並且爲該對象注入其所依賴的屬性。
這裏其實就是主幹邏輯,我們前面圖中已經標明,在整個過程中會調用三次doGetBean()方法
第一次調用的時候會嘗試獲取A對象實例,此時走的是第一個getSingleton()方法,由於沒有已經創建的A對象的成品或半成品,因而這裏得到的是null
然後就會調用第二個getSingleton()方法,創建A對象的實例,然後遞歸的調用doGetBean()方法,嘗試獲取B對象的實例以注入到A對象中
此時由於Spring容器中也沒有B對象的成品或半成品,因而還是會走到第二個getSingleton()方法,在該方法中創建B對象的實例
創建完成之後,嘗試獲取其所依賴的A的實例作爲其屬性,因而還是會遞歸的調用doGetBean()方法
此時需要注意的是,在前面由於已經有了一個半成品的A對象的實例,因而這個時候,再嘗試獲取A對象的實例的時候,會走第一個getSingleton()方法
在該方法中會得到一個半成品的A對象的實例,然後將該實例返回,並且將其注入到B對象的屬性a中,此時B對象實例化完成。
然後,將實例化完成的B對象遞歸的返回,此時就會將該實例注入到A對象中,這樣就得到了一個成品的A對象。
我們這裏可以閱讀上面的第一個getSingleton()方法:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 嘗試從緩存中獲取成品的目標對象,如果存在,則直接返回
Object singletonObject = this.singletonObjects.get(beanName);
// 如果緩存中不存在目標對象,則判斷當前對象是否已經處於創建過程中,在前面的講解中,第一次嘗試獲取A對象
// 的實例之後,就會將A對象標記爲正在創建中,因而最後再嘗試獲取A對象的時候,這裏的if判斷就會爲true
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 這裏的singletonFactories是一個Map,其key是bean的名稱,而值是一個ObjectFactory類型的
// 對象,這裏對於A和B而言,調用圖其getObject()方法返回的就是A和B對象的實例,無論是否是半成品
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 獲取目標對象的實例
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
這裏我們會存在一個問題就是A的半成品實例是如何實例化的,然後是如何將其封裝爲一個ObjectFactory類型的對象,並且將其放到上面的singletonFactories屬性中的。
這主要是在前面的第二個getSingleton()方法中,其最終會通過其傳入的第二個參數,從而調用createBean()方法,該方法的最終調用是委託給了另一個doCreateBean()方法進行的
這裏面有如下一段代碼:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 實例化當前嘗試獲取的bean對象,比如A對象和B對象都是在這裏實例化的
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 判斷Spring是否配置了支持提前暴露目標bean,也就是是否支持提前暴露半成品的bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences
&& isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 如果支持,這裏就會將當前生成的半成品的bean放到singletonFactories中,這個singletonFactories
// 就是前面第一個getSingleton()方法中所使用到的singletonFactories屬性,也就是說,這裏就是
// 封裝半成品的bean的地方。而這裏的getEarlyBeanReference()本質上是直接將放入的第三個參數,也就是
// 目標bean直接返回
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
try {
// 在初始化實例之後,這裏就是判斷當前bean是否依賴了其他的bean,如果依賴了,
// 就會遞歸的調用getBean()方法嘗試獲取目標bean
populateBean(beanName, mbd, instanceWrapper);
} catch (Throwable ex) {
// 省略...
}
return exposedObject;
}
總結一下
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三級緩存的前提是執行了構造器,所以構造器的循環依賴沒法解決