https://github.com/javahongxi
3.2.1 什麼是循環依賴
循環依賴就是循環引用,就是兩個或多個Bean相互之間的持有對方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,則它們最終反映爲一個環。此處不是循環調用,循環調用是方法之間的環調用。如圖3-5所示:
圖3-5 循環引用
循環調用是無法解決的,除非有終結條件,否則就是死循環,最終導致內存溢出錯誤。
Spring容器循環依賴包括構造器循環依賴和setter循環依賴,那Spring容器如何解決循環依賴呢?首先讓我們來定義循環引用類:
package cn.javass.spring.chapter3.bean;
public class CircleA {
private CircleB circleB;
public CircleA() {
}
public CircleA(CircleB circleB) {
this.circleB = circleB;
}
public void setCircleB(CircleB circleB)
{
this.circleB = circleB;
}
public void a() {
circleB.b();
}
}
package cn.javass.spring.chapter3.bean;
public class CircleB {
private CircleC circleC;
public CircleB() {
}
public CircleB(CircleC circleC) {
this.circleC = circleC;
}
public void setCircleC(CircleC circleC)
{
this.circleC = circleC;
}
public void b() {
circleC.c();
}
}
package cn.javass.spring.chapter3.bean;
public class CircleC {
private CircleA circleA;
public CircleC() {
}
public CircleC(CircleA circleA) {
this.circleA = circleA;
}
public void setCircleA(CircleA circleA)
{
this.circleA = circleA;
}
public void c() {
circleA.a();
}
}
3.2.2 Spring如何解決循環依賴
一、構造器循環依賴:表示通過構造器注入構成的循環依賴,此依賴是無法解決的,只能拋出BeanCurrentlyInCreationException異常表示循環依賴。
如在創建CircleA類時,構造器需要CircleB類,那將去創建CircleB,在創建CircleB類時又發現需要CircleC類,則又去創建CircleC,最終在創建CircleC時發現又需要CircleA;從而形成一個環,沒辦法創建。
Spring容器將每一個正在創建的Bean 標識符放在一個“當前創建Bean池”中,Bean標識符在創建過程中將一直保持在這個池中,因此如果在創建Bean過程中發現自己已經在“當前創建Bean池”裏時將拋出BeanCurrentlyInCreationException異常表示循環依賴;而對於創建完畢的Bean將從“當前創建Bean池”中清除掉。
1)首先讓我們看一下配置文件(chapter3/circleInjectByConstructor.xml):
<bean id="circleA" class="cn.javass.spring.chapter3.bean.CircleA">
<constructor-arg index="0" ref="circleB"/>
</bean>
<bean id="circleB" class="cn.javass.spring.chapter3.bean.CircleB">
<constructor-arg index="0" ref="circleC"/>
</bean>
<bean id="circleC" class="cn.javass.spring.chapter3.bean.CircleC">
<constructor-arg index="0" ref="circleA"/>
</bean>
2)寫段測試代碼(cn.javass.spring.chapter3.CircleTest)測試一下吧:
@Test (expected = BeanCurrentlyInCreationException.class)
public void testCircleByConstructor() throws Throwable {
try {
new ClassPathXmlApplicationContext("chapter3/circleInjectByConstructor.xml");
}
catch (Exception e) {
//因爲要在創建circle3時拋出;
Throwable e1 = e.getCause().getCause().getCause();
throw e1;
}
}
讓我們分析一下吧:
1、Spring容器創建“circleA” Bean,首先去“當前創建Bean池”查找是否當前Bean正在創建,如果沒發現,則繼續準備其需要的構造器參數“circleB”,並將“circleA” 標識符放到“當前創建Bean池”;
2、Spring容器創建“circleB” Bean,首先去“當前創建Bean池”查找是否當前Bean正在創建,如果沒發現,則繼續準備其需要的構造器參數“circleC”,並將“circleB” 標識符放到“當前創建Bean池”;
3、Spring容器創建“circleC” Bean,首先去“當前創建Bean池”查找是否當前Bean正在創建,如果沒發現,則繼續準備其需要的構造器參數“circleA”,並將“circleC” 標識符放到“當前創建Bean池”;
4、到此爲止Spring容器要去創建“circleA”Bean,發現該Bean 標識符在“當前創建Bean池”中,因爲表示循環依賴,拋出BeanCurrentlyInCreationException。
二、setter循環依賴:表示通過setter注入方式構成的循環依賴。
對於setter注入造成的依賴是通過Spring容器提前暴露剛完成構造器注入但未完成其他步驟(如setter注入)的Bean來完成的,而且只能解決單例作用域的Bean循環依賴。
如下代碼所示,通過提前暴露一個單例工廠方法,從而使其他Bean能引用到該Bean。
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
具體步驟如下:
1、Spring容器創建單例“circleA” Bean,首先根據無參構造器創建Bean,並暴露一個“ObjectFactory ”用於返回一個提前暴露一個創建中的Bean,並將“circleA” 標識符放到“當前創建Bean池”;然後進行setter注入“circleB”;
2、Spring容器創建單例“circleB” Bean,首先根據無參構造器創建Bean,並暴露一個“ObjectFactory”用於返回一個提前暴露一個創建中的Bean,並將“circleB” 標識符放到“當前創建Bean池”,然後進行setter注入“circleC”;
3、Spring容器創建單例“circleC” Bean,首先根據無參構造器創建Bean,並暴露一個“ObjectFactory ”用於返回一個提前暴露一個創建中的Bean,並將“circleC” 標識符放到“當前創建Bean池”,然後進行setter注入“circleA”;進行注入“circleA”時由於提前暴露了“ObjectFactory”工廠從而使用它返回提前暴露一個創建中的Bean;
4、最後在依賴注入“circleB”和“circleA”,完成setter注入。
對於“prototype”作用域Bean,Spring容器無法完成依賴注入,因爲“prototype”作用域的Bean,Spring容器不進行緩存,因此無法提前暴露一個創建中的Bean。
<!-- 定義Bean配置文件,注意scope都是“prototype”-->
<bean id="circleA" class="cn.javass.spring.chapter3.bean.CircleA" scope="prototype">
<property name="circleB" ref="circleB"/>
</bean>
<bean id="circleB" class="cn.javass.spring.chapter3.bean.CircleB" scope="prototype">
<property name="circleC" ref="circleC"/>
</bean>
<bean id="circleC" class="cn.javass.spring.chapter3.bean.CircleC" scope="prototype">
<property name="circleA" ref="circleA"/>
</bean>
// 測試代碼cn.javass.spring.chapter3.CircleTest
@Test (expected = BeanCurrentlyInCreationException.class)
public void testCircleBySetterAndPrototype () throws Throwable {
try {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
"chapter3/circleInjectBySetterAndPrototype.xml");
System.out.println(ctx.getBean("circleA"));
}
catch (Exception e) {
Throwable e1 = e.getCause().getCause().getCause();
throw e1;
}
}
對於“singleton”作用域Bean,可以通過“setAllowCircularReferences(false);”來禁用循環引用:
@Test (expected = BeanCurrentlyInCreationException.class)
public void testCircleBySetterAndSingleton2() throws Throwable {
try {
ClassPathXmlApplicationContext ctx =
new ClassPathXmlApplicationContext();
ctx.setConfigLocation("chapter3/circleInjectBySetterAndSingleton.xml");
ctx.refresh();
}
catch (Exception e) {
Throwable e1 = e.getCause().getCause().getCause();
throw e1;
}
}
補充:出現循環依賴是設計上的問題,一定要避免!
請參考《敏捷軟件開發:原則、模式與實踐》中的“無環依賴”原則
包之間的依賴結構必須是一個直接的無環圖形(DAG)。也就是說,在依賴結構中不允許出現環(循環依賴)。
原創內容 轉載請註明出處【http://sishuok.com/forum/blogPost/list/0/2448.html#7070】