spring解決循環(huán)依賴的簡(jiǎn)單方法
Spring內(nèi)部如何解決循環(huán)依賴,一定是單默認(rèn)的單例Bean中,屬性互相引用的場(chǎng)景。比如幾個(gè)Bean之間的互相引用:

或者

setter方式原型,prototype
原型(Prototype)的場(chǎng)景是不支持循環(huán)依賴的,因?yàn)椤皃rototype”作用域的Bean,為每一個(gè)bean請(qǐng)求提供一個(gè)實(shí)例,Spring容器不進(jìn)行緩存,因此無法提前暴露一個(gè)創(chuàng)建中的Bean,會(huì)拋出異常。
構(gòu)造器參數(shù)循環(huán)依賴
Spring容器會(huì)將每一個(gè)正在創(chuàng)建的Bean 標(biāo)識(shí)符放在一個(gè)“當(dāng)前創(chuàng)建Bean池”中,Bean標(biāo)識(shí)符在創(chuàng)建過程中將一直保持在這個(gè)池中。
因此如果在創(chuàng)建Bean過程中發(fā)現(xiàn)自己已經(jīng)在“當(dāng)前創(chuàng)建Bean池”里時(shí)將拋出BeanCurrentlyInCreationException異常表示循環(huán)依賴;而對(duì)于創(chuàng)建完畢的Bean將從“當(dāng)前創(chuàng)建Bean池”中清除掉。
Spring容器先創(chuàng)建單例A,A依賴B,然后將A放在“當(dāng)前創(chuàng)建Bean池”中,此時(shí)創(chuàng)建B,B依賴C,然后將B放在“當(dāng)前創(chuàng)建Bean池”中,此時(shí)創(chuàng)建C,C又依賴A, 但是,此時(shí)A已經(jīng)在池中,所以會(huì)報(bào)錯(cuò),,因?yàn)樵诔刂械腂ean都是未初始化完的,所以會(huì)依賴錯(cuò)誤 ,(初始化完的Bean會(huì)從池中移除)。
setter方式單例,默認(rèn)方式
那么默認(rèn)單例的屬性注入場(chǎng)景,Spring是如何支持循環(huán)依賴的?
既然,要解決循環(huán)依賴,那肯定存在著依賴,我們假設(shè)有兩個(gè)類:
A和B,A->B,B->A,且二者是通過@Autowired相互注入的。
我們假設(shè)從A開始創(chuàng)建,那就是先創(chuàng)建A對(duì)象,然后創(chuàng)建B對(duì)象,再通過反射把B對(duì)象set到A對(duì)象的屬性上去(fieldB.set(a, b)),在創(chuàng)建B的時(shí)候發(fā)現(xiàn)它又依賴于A,這時(shí)候同樣地,我要尋找一個(gè)A對(duì)象set到B的屬性上去,但是我們的系統(tǒng)中只能存在一個(gè)A對(duì)象(單例)。
為了解決這個(gè)問題,需要再前面創(chuàng)建A對(duì)象的時(shí)候就把它保存起來?我們假設(shè)保存在緩存中,后面B對(duì)象要使用的時(shí)候先去緩存中查找一下不就OK了嘛?!
所以,解決循環(huán)依賴的方法就是保存所有創(chuàng)建的對(duì)象,后面創(chuàng)建對(duì)象的時(shí)候有依賴的情況先去緩存中找一下,找到了直接set到那個(gè)正在創(chuàng)建的對(duì)象的屬性上,沒找到就創(chuàng)建一個(gè)新的對(duì)象給那個(gè)正在創(chuàng)建的對(duì)象,并保存到緩存中。
實(shí)際上,Spring中也是這么干的,只不過它的緩存不只一個(gè),而是有四個(gè)。
具體可以看下這邊文章。
http://www.dbjr.com.cn/article/195897.htm
Spring解決循環(huán)依賴
首先,Spring內(nèi)部維護(hù)了三個(gè)Map,也就是我們通常說的三級(jí)緩存。
在Spring的DefaultSingletonBeanRegistry類中,類上方掛著這三個(gè)Map:
- singletonObjects 它是我們最熟悉的朋友,俗稱“單例池”“容器”,緩存創(chuàng)建完成單例Bean的地方。
- singletonFactories 映射創(chuàng)建Bean的原始工廠
- earlySingletonObjects 映射Bean的早期引用,也就是說在這個(gè)Map里的Bean不是完整的,甚至還不能稱之為“Bean”,只是一個(gè)Instance.
后兩個(gè)Map其實(shí)是“墊腳石”級(jí)別的,只是創(chuàng)建Bean的時(shí)候,用來借助了一下,創(chuàng)建完成就清掉了。
為什么成為后兩個(gè)Map為墊腳石,假設(shè)最終放在singletonObjects的Bean是你想要的一杯“涼白開”。
那么Spring準(zhǔn)備了兩個(gè)杯子,即singletonFactories和earlySingletonObjects來回“倒騰”幾番,把熱水晾成“涼白開”放到singletonObjects中
循環(huán)依賴的本質(zhì)
定義兩個(gè)類A與B:
public class A {
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
public class B {
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
private A a;
}
public class CircularDependency {
private static Map<String, Object> cacheMap = new HashMap<>(2);
public static void main(String[] args) throws Exception {
// 假裝掃描出來的對(duì)象
Class[] classes = { A.class, B.class };
// 假裝項(xiàng)目初始化實(shí)例化所有bean
for (Class aClass : classes) {
getBean(aClass);
}
// check
System.out.println(getBean(B.class).getA() == getBean(A.class));
System.out.println(getBean(A.class).getB() == getBean(B.class));
}
private static <T> T getBean(Class<T> beanClass) throws Exception{
// 本文用類名小寫 簡(jiǎn)單代替bean的命名規(guī)則
String beanName = beanClass.getSimpleName().toLowerCase();
// 如果已經(jīng)是一個(gè)bean,則直接返回
if (cacheMap.containsKey(beanName)) {
return (T) cacheMap.get(beanName);
}
// 將對(duì)象本身實(shí)例化
Object object = beanClass.getDeclaredConstructor().newInstance();
// 放入緩存
cacheMap.put(beanName, object);
// 把所有字段當(dāng)成需要注入的bean,創(chuàng)建并注入到當(dāng)前bean中
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
// 獲取需要注入字段的class
Class<?> fieldClass = field.getType();
String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
// 如果需要注入的bean,已經(jīng)在緩存Map中,那么把緩存Map中的值注入到該field即可
// 如果緩存沒有 繼續(xù)創(chuàng)建
field.set(object, cacheMap.containsKey(fieldBeanName) ? cacheMap.get(fieldBeanName) : getBean(fieldClass));
}
// 屬性填充完成,返回
return (T) object;
}
}
這段代碼的效果,其實(shí)就是處理了循環(huán)依賴,并且處理完成后,cacheMap中放的就是完整的“Bean”了。
總結(jié)
到此這篇關(guān)于spring解決循環(huán)依賴的文章就介紹到這了,更多相關(guān)spring解決循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring為何要用三級(jí)緩存來解決循環(huán)依賴問題
- 淺談Spring 解決循環(huán)依賴必須要三級(jí)緩存嗎
- spring解決循環(huán)依賴
- Spring IOC原理補(bǔ)充說明(循環(huán)依賴、Bean作用域等)
- Spring如何解決循環(huán)依賴的問題
- 簡(jiǎn)單了解Spring beanfactory循環(huán)依賴命名重復(fù)屬性
- Spring循環(huán)依賴的解決辦法,你真的懂了嗎
- 基于SpringBoot構(gòu)造器注入循環(huán)依賴及解決方式
- 淺談Spring如何解決循環(huán)依賴的問題
- 詳解Spring循環(huán)依賴的解決方案
- spring 如何解決循環(huán)依賴
相關(guān)文章
spring boot實(shí)現(xiàn)自動(dòng)輸出word文檔功能的實(shí)例代碼
這篇文章主要介紹了spring boot實(shí)現(xiàn)自動(dòng)輸出word文檔功能的實(shí)例代碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
Struts2實(shí)現(xiàn)文件上傳功能實(shí)例解析
這篇文章主要介紹了Struts2實(shí)現(xiàn)文件上傳功能實(shí)例解析,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01
java統(tǒng)計(jì)文件中每個(gè)字符出現(xiàn)的個(gè)數(shù)
這篇文章主要為大家詳細(xì)介紹了java統(tǒng)計(jì)文件中每個(gè)字符出現(xiàn)的個(gè)數(shù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03
Java中實(shí)現(xiàn)文件預(yù)覽的功能(實(shí)例代碼)
大家都知道word,Excel,PPT實(shí)現(xiàn)在線預(yù)覽常用的方式就是先轉(zhuǎn)換成pdf,然后在進(jìn)行預(yù)覽,下面給大家介紹Java中如何實(shí)現(xiàn)文件預(yù)覽的功能,需要的朋友可以參考下2023-05-05
SpringBoot集成IJPay實(shí)現(xiàn)微信v3支付的示例代碼
本文主要介紹了SpringBoot集成IJPay實(shí)現(xiàn)微信v3支付的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07
maven項(xiàng)目下solr和spring的整合配置詳解
這篇文章主要介紹了maven項(xiàng)目下solr和spring的整合配置詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-11-11
idea創(chuàng)建spring boot工程及配置文件(最新推薦)
本文給大家介紹idea創(chuàng)建spring boot工程及配置文件,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-11-11
ssm項(xiàng)目改造spring?boot項(xiàng)目完整步驟
Spring?Boot現(xiàn)在已經(jīng)成為Java開發(fā)領(lǐng)域的一顆璀璨明珠,它本身是包容萬(wàn)象的,可以跟各種技術(shù)集成,下面這篇文章主要給大家介紹了關(guān)于ssm項(xiàng)目改造spring?boot項(xiàng)目的相關(guān)資料,需要的朋友可以參考下2023-04-04

