Spring原始碼解析-6、spring單例如何解決迴圈依賴
什麼叫迴圈依賴
迴圈依賴即兩個及以上的bean物件互相持有對方的引用,最終形成一個閉環。
spring如何處理正在建立的Bean
Spring容器會將每一個正在建立的Bean 識別符號放在一個“當前建立Bean池”中,Bean識別符號在建立過程中將一直保持
在這個池中,因此如果在建立Bean過程中發現自己已經在“當前建立Bean池”裡時將丟擲
BeanCurrentlyInCreationException異常表示迴圈依賴;而對於建立完畢的Bean將從“當前建立Bean池”中清除掉。
迴圈依賴的三種情況
構造器
public class BeanA { private BeanB b; public BeanA(BeanB b) { this.b = b; } } public class BeanB { private BeanC c; public BeanB(BeanC c) { this.c = c; } } public class BeanC { private BeanA a; public BeanC(BeanA a) { this.a = a; } } ---------------------- <bean id="a" class="com.raycloud.dmj.data.utils.BeanA"> <constructor-arg index="0" ref="b"/> </bean> <bean id="b" class="com.raycloud.dmj.data.utils.BeanB"> <constructor-arg index="0" ref="c"/> </bean> <bean id="c" class="com.raycloud.dmj.data.utils.BeanC"> <constructor-arg index="0" ref="a"/> </bean> ---------------------- ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
報錯:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?
Spring容器先建立單例BeanA,BeanA依賴BeanB,然後將A放在“當前建立Bean池”中,此時建立BeanB,BeanB依賴BeanC ,然後將B放在“當前建立Bean池”中,此時建立BeanC,StudentC又依賴BeanA, 但是,此時BeanA已經在池中,所以會報錯,,因為在池中的Bean都是未初始化完的,所以會依賴錯誤 ,(初始化完的Bean會從池中移除)
單例setter
public class BeanA { public BeanB b; public void setB(BeanB b) { this.b = b; } } public class BeanB { public BeanC c; public void setC(BeanC c) { this.c = c; } } public class BeanC { public BeanA a; public void setA(BeanA a) { this.a = a; } } ---------------------- <bean id="a" class="com.raycloud.dmj.data.utils.BeanA"> <property name="b" ref="b"/> </bean> <bean id="b" class="com.raycloud.dmj.data.utils.BeanB"> <property name="c" ref="c"/> </bean> <bean id="c" class="com.raycloud.dmj.data.utils.BeanC"> <property name="a" ref="a"/> </bean> ---------------------- ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); BeanA a = context.getBean("a", BeanA.class); BeanB b = context.getBean("b", BeanB.class); BeanC c = context.getBean("c", BeanC.class); System.out.println(String.format("a:%s,a.b:%s",a,a.b)); System.out.println(String.format("b:%s,b.c:%s",b,b.c)); System.out.println(String.format("c:%s,c.a:%s",c,c.a));
輸出:
a:[email protected],a.b:[email protected]
b:[email protected],b.c:[email protected]
c:[email protected],c.a:[email protected]
可以看到單例setter迴圈依賴沒有報錯,且迴圈的依賴都成功set。具體實現原理後面詳細看
原型setter
<bean id="a" class="com.raycloud.dmj.data.utils.BeanA" scope="prototype">
<property name="b" ref="b"/>
</bean>
<bean id="b" class="com.raycloud.dmj.data.utils.BeanB" scope="prototype">
<property name="c" ref="c"/>
</bean>
<bean id="c" class="com.raycloud.dmj.data.utils.BeanC" scope="prototype">
<property name="a" ref="a"/>
</bean>
同樣報錯:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘a’: Requested bean is currently in creation: Is there an unresolvable circular reference?
因此可以知道多例模式下也不能解決迴圈依賴。
為什麼?
對於“prototype”作用域Bean,Spring容器無法完成依賴注入,因為“prototype”作用域的Bean,Spring容
器不進行快取,因此無法提前暴露一個建立中的Bean。
單例模式如何解決迴圈依賴
如圖,set方法注入,spring先例項化Bean[通過無參構造器],在設定屬性,這樣就不會報錯了。
具體如何解決迴圈依賴
以我們的例項來說,spring會先例項化a,b,c,並放入一個map,然後設定屬性,a設定屬性b,只需要從mao中取出b即可,以此類推。
而事實上spring可不止這一個map,而是通過三級快取來解決單例Bean的迴圈依賴。
三級快取
/** 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);
singletonObjects:存放單例物件例項的快取
earlySingletonObjects:存在提前曝光的bean,也就是正在建立中的bean。
singletonFactories :建立單例物件的工廠
實現原理
實現原理的程式碼如下:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//先從單例快取中取
Object singletonObject = this.singletonObjects.get(beanName);
//一級快取中沒有,並且判斷正在建立中【比如A的構造器依賴B,或者已經例項化正在setB ,所以先去建立B,那麼A就是建立中】
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;
}
簡單來說:
在建立單例Bean的時候是這樣解決迴圈依賴的。
假設A B互相依賴。
先通過createBean建立A,會先走createInstance來例項化A,然後把A的單例工廠放到三級快取,例項化後需要設定屬性,發現需要B,但是B沒有初始化,因此通過createBean建立,同樣需要例項化,例項化以後發現依賴A,因此先去單例快取中找,因為A還在建立中,所以找不到,然後去二級快取找,依舊找不到,因此最後通過單例工廠建立獲取了A,B就建立好了,B建立好了,就set給A,此時A.B都例項化成功了。