# 【第三章】 DI 之 3.2 循環依賴 ——跟我學spring3
### 3.2.1? 什么是循環依賴
循環依賴就是循環引用,就是兩個或多個Bean相互之間的持有對方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,則它們最終反映為一個環。此處不是循環調用,循環調用是方法之間的環調用。如圖3-5所示:

圖3-5 循環引用
循環調用是無法解決的,除非有終結條件,否則就是死循環,最終導致內存溢出錯誤。
Spring容器循環依賴包括構造器循環依賴和setter循環依賴,那Spring容器如何解決循環依賴呢?首先讓我們來定義循環引用類:
1. package?cn.javass.spring.chapter3.bean;??
2. public?class?CircleA?{??
3. private?CircleB?circleB;??
4. public?CircleA()?{??
5. }??
6. public?CircleA(CircleB?circleB)?{??
7. this.circleB?=?circleB;??
8. }??
9. public?void?setCircleB(CircleB?circleB)???
10. {??
11. this.circleB?=?circleB;??
12. }??
13. public?void?a()?{??
14. circleB.b();??
15. }??
16. }??
1. package?cn.javass.spring.chapter3.bean;??
2. public?class?CircleB?{??
3. private?CircleC?circleC;??
4. public?CircleB()?{??
5. }??
6. public?CircleB(CircleC?circleC)?{??
7. this.circleC?=?circleC;??
8. }??
9. public?void?setCircleC(CircleC?circleC)???
10. {??
11. this.circleC?=?circleC;??
12. }??
13. public?void?b()?{??
14. circleC.c();??
15. }??
16. }??
1. package?cn.javass.spring.chapter3.bean;??
2. public?class?CircleC?{??
3. private?CircleA?circleA;??
4. public?CircleC()?{??
5. }??
6. public?CircleC(CircleA?circleA)?{??
7. this.circleA?=?circleA;??
8. }??
9. public?void?setCircleA(CircleA circleA)???
10. {??
11. this.circleA?=?circleA;??
12. }??
13. public?void?c()?{??
14. circleA.a();??
15. }??
16. }??
### 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):
2. <bean?id="circleA"?class="cn.javass.spring.chapter3.bean.CircleA">??
3. <constructor-arg?index="0"?ref="circleB"/>??
4. </bean>??
5. <bean?id="circleB"?class="cn.javass.spring.chapter3.bean.CircleB">??
6. <constructor-arg?index="0"?ref="circleC"/>??
7. </bean>??
8. <bean?id="circleC"?class="cn.javass.spring.chapter3.bean.CircleC">??
9. <constructor-arg?index="0"?ref="circleA"/>??
10. </bean>??
2)寫段測試代碼(cn.javass.spring.chapter3.CircleTest)測試一下吧:
1. @Test(expected?=?BeanCurrentlyInCreationException.class)??
2. public?void?testCircleByConstructor()?throws?Throwable?{??
3. try?{??
4. new?ClassPathXmlApplicationContext("chapter3/circleInjectByConstructor.xml");??
5. }??
6. catch?(Exception?e)?{??
7. //因為要在創建circle3時拋出;??
8. Throwable?e1?=?e.getCause().getCause().getCause();??
9. throw?e1;??
10. }??
11. }??
讓我們分析一下吧:
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。
1. addSingletonFactory(beanName,?new?ObjectFactory()?{??
2. public?Object?getObject()?throws?BeansException?{??
3. return?getEarlyBeanReference(beanName,?mbd,?bean);??
4. }??
5. });??
具體步驟如下:
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。
1. <!--?定義Bean配置文件,注意scope都是“prototype”-->??
2. <bean?id="circleA"?class="cn.javass.spring.chapter3.bean.CircleA"?scope="prototype">??
3. <property?name="circleB"?ref="circleB"/>??
4. </bean>??
5. <bean?id="circleB"?class="cn.javass.spring.chapter3.bean.CircleB"?scope="prototype">??
6. <property?name="circleC"?ref="circleC"/>??
7. </bean>??
8. <bean?id="circleC"?class="cn.javass.spring.chapter3.bean.CircleC"?scope="prototype">??
9. <property?name="circleA"?ref="circleA"/>??
10. </bean>??
1. //測試代碼cn.javass.spring.chapter3.CircleTest??
2. @Test(expected?=?BeanCurrentlyInCreationException.class)??
3. public?void?testCircleBySetterAndPrototype?()?throws?Throwable?{??
4. try?{??
5. ClassPathXmlApplicationContext?ctx?=?new?ClassPathXmlApplicationContext(??
6. "chapter3/circleInjectBySetterAndPrototype.xml");??
7. System.out.println(ctx.getBean("circleA"));??
8. }??
9. catch?(Exception?e)?{??
10. Throwable?e1?=?e.getCause().getCause().getCause();??
11. throw?e1;??
12. }??
13. }??
對于“singleton”作用域Bean,可以通過“setAllowCircularReferences(false);”來禁用循環引用:
1. @Test(expected?=?BeanCurrentlyInCreationException.class)??
2. public?void?testCircleBySetterAndSingleton2()?throws?Throwable?{??
3. try?{??
4. ClassPathXmlApplicationContext?ctx?=??
5. new?ClassPathXmlApplicationContext();??
6. ctx.setConfigLocation("chapter3/circleInjectBySetterAndSingleton.xml");??
7. ctx.refresh();??
8. }??
9. catch?(Exception?e)?{??
10. Throwable?e1?=?e.getCause().getCause().getCause();??
11. throw?e1;??
12. }??
13. }??
補充:出現循環依賴是設計上的問題,一定要避免!
請參考《敏捷軟件開發:原則、模式與實踐》中的“無環依賴”原則
包之間的依賴結構必須是一個直接的無環圖形(DAG)。也就是說,在依賴結構中不允許出現環(循環依賴)。?
原創內容 轉載請注明出處【[http://sishuok.com/forum/blogPost/list/0/2448.html#7070](http://sishuok.com/forum/blogPost/list/0/2448.html#7070)】
- 跟我學 Spring3
- 【第二章】 IoC 之 2.1 IoC基礎 ——跟我學Spring3
- 【第二章】 IoC 之 2.2 IoC 容器基本原理 ——跟我學Spring3
- 【第二章】 IoC 之 2.3 IoC的配置使用——跟我學Spring3
- 【第三章】 DI 之 3.1 DI的配置使用 ——跟我學spring3
- 【第三章】 DI 之 3.2 循環依賴 ——跟我學spring3
- 【第三章】 DI 之 3.3 更多DI的知識 ——跟我學spring3
- 【第三章】 DI 之 3.4 Bean的作用域 ——跟我學spring3
- 【第四章】 資源 之 4.1 基礎知識 ——跟我學spring3
- 【第四章】 資源 之 4.2 內置Resource實現 ——跟我學spring3
- 【第四章】 資源 之 4.3 訪問Resource ——跟我學spring3
- 【第四章】 資源 之 4.4 Resource通配符路徑 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.1 概述 5.2 SpEL基礎 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.3 SpEL語法 ——跟我學spring3
- 【第五章】Spring表達式語言 之 5.4在Bean定義中使用EL—跟我學spring3
- 【第六章】 AOP 之 6.1 AOP基礎 ——跟我學spring3
- 【第六章】 AOP 之 6.2 AOP的HelloWorld ——跟我學spring3
- 【第六章】 AOP 之 6.3 基于Schema的AOP ——跟我學spring3
- 【第六章】 AOP 之 6.4 基于@AspectJ的AOP ——跟我學spring3
- 【第六章】 AOP 之 6.5 AspectJ切入點語法詳解 ——跟我學spring3
- 【第六章】 AOP 之 6.6 通知參數 ——跟我學spring3
- 【第六章】 AOP 之 6.7 通知順序 ——跟我學spring3
- 【第六章】 AOP 之 6.8 切面實例化模型 ——跟我學spring3
- 【第六章】 AOP 之 6.9 代理機制 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.1 概述 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.2 JDBC模板類 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.3 關系數據庫操作對象化 ——跟我學spring3
- 【第七章】 對JDBC的支持 之 7.4 Spring提供的其它幫助 ——跟我學spring3【私塾在線原創】
- 【第七章】 對JDBC的支持 之 7.5 集成Spring JDBC及最佳實踐 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.1 概述 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.2 集成Hibernate3 ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.3 集成iBATIS ——跟我學spring3
- 【第八章】 對ORM的支持 之 8.4 集成JPA ——跟我學spring3
- 【第九章】 Spring的事務 之 9.1 數據庫事務概述 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.2 事務管理器 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.3 編程式事務 ——跟我學spring3
- 【第九章】 Spring的事務 之 9.4 聲明式事務 ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.1 概述 ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.2 集成Struts1.x ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.3 集成Struts2.x ——跟我學spring3
- 【第十章】集成其它Web框架 之 10.4 集成JSF ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.1 概述 ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.2 實現通用層 ——跟我學spring3
- 【第十一章】 SSH集成開發積分商城 之 11.3 實現積分商城層 ——跟我學spring3
- 【第十二章】零配置 之 12.1 概述 ——跟我學spring3
- 【第十二章】零配置 之 12.2 注解實現Bean依賴注入 ——跟我學spring3
- 【第十二章】零配置 之 12.3 注解實現Bean定義 ——跟我學spring3
- 【第十二章】零配置 之 12.4 基于Java類定義Bean配置元數據 ——跟我學spring3
- 【第十二章】零配置 之 12.5 綜合示例-積分商城 ——跟我學spring3
- 【第十三章】 測試 之 13.1 概述 13.2 單元測試 ——跟我學spring3
- 【第十三章】 測試 之 13.3 集成測試 ——跟我學spring3
- 跟我學 Spring MVC
- SpringMVC + spring3.1.1 + hibernate4.1.0 集成及常見問題總結
- Spring Web MVC中的頁面緩存支持 ——跟我學SpringMVC系列
- Spring3 Web MVC下的數據類型轉換(第一篇)——《跟我學Spring3 Web MVC》搶先看
- Spring3 Web MVC下的數據格式化(第二篇)——《跟我學Spring3 Web MVC》搶先看
- 第一章 Web MVC簡介 —— 跟開濤學SpringMVC
- 第二章 Spring MVC入門 —— 跟開濤學SpringMVC
- 第三章 DispatcherServlet詳解 ——跟開濤學SpringMVC
- 第四章 Controller接口控制器詳解(1)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(2)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(3)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解 (4)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(5)——跟著開濤學SpringMVC
- 跟著開濤學SpringMVC 第一章源代碼下載
- 第二章 Spring MVC入門 源代碼下載
- 第四章 Controller接口控制器詳解 源代碼下載
- 第四章 Controller接口控制器詳解(6)——跟著開濤學SpringMVC
- 第四章 Controller接口控制器詳解(7 完)——跟著開濤學SpringMVC
- 第五章 處理器攔截器詳解——跟著開濤學SpringMVC
- 源代碼下載 第五章 處理器攔截器詳解——跟著開濤學SpringMVC
- 注解式控制器運行流程及處理器定義 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- 源代碼下載 第六章 注解式控制器詳解
- SpringMVC3強大的請求映射規則詳解 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- Spring MVC 3.1新特性 生產者、消費者請求限定 —— 第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC強大的數據綁定(1)——第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC強大的數據綁定(2)——第六章 注解式控制器詳解——跟著開濤學SpringMVC
- SpringMVC數據類型轉換——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC
- SpringMVC數據格式化——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC
- SpringMVC數據驗證——第七章 注解式控制器的數據驗證、類型轉換及格式化——跟著開濤學SpringMVC