Spring對循環依賴的處理

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章