?
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。
?
- addSingletonFactory(beanName,? new ?ObjectFactory()?{??
- ???? 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;??
- ????}??
- }??
?
補充:出現循環依賴是設計上的問題,一定要避免!
請參考《敏捷軟件開發:原則、模式與實踐》中的“無環依賴”原則
?
?原創內容 轉載請注明出處【 http://sishuok.com/forum/blogPost/list/0/2448.html#7070 】
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
