深入剖析Spring如何解決循環(huán)依賴(lài)
一、什么是循環(huán)依賴(lài)
循環(huán)依賴(lài)(Circular Dependency)是指兩個(gè)或多個(gè)Bean相互依賴(lài),形成一個(gè)閉環(huán)的情況。例如:
@Service
public class AService {
@Autowired
private BService bService;
}
@Service
public class BService {
@Autowired
private AService aService;
}
上述代碼中,AService依賴(lài)BService,而B(niǎo)Service又依賴(lài)AService,形成了循環(huán)依賴(lài)。
二、Spring循環(huán)依賴(lài)的三種情況
構(gòu)造器循環(huán)依賴(lài):通過(guò)構(gòu)造器注入形成的循環(huán)依賴(lài),Spring無(wú)法解決,會(huì)直接拋出BeanCurrentlyInCreationException
Setter循環(huán)依賴(lài)(單例模式):通過(guò)setter方法注入形成的循環(huán)依賴(lài),Spring可以解決
原型模式(prototype)循環(huán)依賴(lài):scope為prototype的bean形成的循環(huán)依賴(lài),Spring無(wú)法解決
三、Spring解決循環(huán)依賴(lài)的核心思想
Spring通過(guò)三級(jí)緩存機(jī)制來(lái)解決單例模式下的Setter循環(huán)依賴(lài)問(wèn)題。核心思想是:
提前暴露未完全初始化的Bean實(shí)例:在Bean創(chuàng)建過(guò)程中,在完成實(shí)例化后、初始化前,將Bean的引用提前暴露
分級(jí)緩存:使用三級(jí)緩存存儲(chǔ)不同狀態(tài)的Bean,確保依賴(lài)注入時(shí)能獲取到正確的引用
四、三級(jí)緩存詳解
1. 三級(jí)緩存結(jié)構(gòu)
/** 一級(jí)緩存:存放完全初始化好的Bean */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** 二級(jí)緩存:存放原始的Bean對(duì)象(尚未填充屬性) */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); /** 三級(jí)緩存:存放Bean工廠對(duì)象,用于生成原始Bean對(duì)象 */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
2. Bean創(chuàng)建與三級(jí)緩存交互流程
1.創(chuàng)建Bean實(shí)例:通過(guò)反射調(diào)用構(gòu)造器創(chuàng)建Bean實(shí)例
2.放入三級(jí)緩存:將Bean包裝成ObjectFactory并放入三級(jí)緩存singletonFactories
3.屬性填充:填充Bean的屬性,此時(shí)如果發(fā)現(xiàn)依賴(lài)其他Bean
- 從一級(jí)緩存singletonObjects獲取
- 如果不存在,嘗試從二級(jí)緩存earlySingletonObjects獲取
- 如果還不存在,從三級(jí)緩存singletonFactories獲取ObjectFactory并生成Bean,然后放入二級(jí)緩存
4.初始化:執(zhí)行初始化方法(@PostConstruct等)
5.放入一級(jí)緩存:將完全初始化好的Bean放入一級(jí)緩存,刪除二、三級(jí)緩存中的記錄
3. 循環(huán)依賴(lài)解決示例
以AService和BService的循環(huán)依賴(lài)為例:
1.開(kāi)始創(chuàng)建AService
- 實(shí)例化AService
- 將AService的ObjectFactory放入三級(jí)緩存
2.填充AService屬性時(shí)發(fā)現(xiàn)需要BService
- 開(kāi)始創(chuàng)建BService
- 實(shí)例化BService
- 將BService的ObjectFactory放入三級(jí)緩存
3.填充BService屬性時(shí)發(fā)現(xiàn)需要AService
- 從一級(jí)緩存獲取AService → 不存在
- 從二級(jí)緩存獲取AService → 不存在
- 從三級(jí)緩存獲取AService的ObjectFactory → 存在,生成AService早期引用并放入二級(jí)緩存
- 將AService早期引用注入BService
4.BService繼續(xù)完成屬性填充和初始化
5.BService創(chuàng)建完成,放入一級(jí)緩存
6.AService獲得完全初始化的BService引用
7.AService繼續(xù)完成屬性填充和初始化
8.AService創(chuàng)建完成,放入一級(jí)緩存
五、源碼分析
關(guān)鍵源碼在DefaultSingletonBeanRegistry類(lèi)中:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 先從一級(jí)緩存獲取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 2. 從二級(jí)緩存獲取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 3. 從三級(jí)緩存獲取ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 將三級(jí)緩存提升到二級(jí)緩存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}六、為什么構(gòu)造器循環(huán)依賴(lài)無(wú)法解決
時(shí)序問(wèn)題:構(gòu)造器注入發(fā)生在實(shí)例化階段,此時(shí)Bean尚未創(chuàng)建完成,無(wú)法提前暴露引用
緩存機(jī)制不適用:三級(jí)緩存機(jī)制依賴(lài)于在實(shí)例化后、初始化前暴露引用,而構(gòu)造器注入時(shí)連實(shí)例都還沒(méi)完全創(chuàng)建
七、為什么原型模式的循環(huán)依賴(lài)無(wú)法解決
生命周期不同:原型模式每次獲取都創(chuàng)建新實(shí)例,Spring不緩存原型Bean
無(wú)法提前暴露引用:沒(méi)有緩存機(jī)制支持,無(wú)法在創(chuàng)建過(guò)程中共享未完成初始化的引用
八、Spring解決循環(huán)依賴(lài)的局限性
只能解決單例模式的Setter注入循環(huán)依賴(lài)
大量使用循環(huán)依賴(lài)會(huì)導(dǎo)致代碼結(jié)構(gòu)混亂,增加維護(hù)難度
某些AOP場(chǎng)景下可能出現(xiàn)問(wèn)題(需要特殊處理)
九、最佳實(shí)踐
避免循環(huán)依賴(lài):通過(guò)設(shè)計(jì)模式(如中介者模式、觀察者模式)重構(gòu)代碼
使用Setter注入替代構(gòu)造器注入:如果必須使用循環(huán)依賴(lài)
使用@Lazy注解:延遲加載依賴(lài)的Bean
@Service
public class AService {
@Autowired
@Lazy
private BService bService;
}
使用ApplicationContextAware接口:手動(dòng)獲取依賴(lài)Bean
十、總結(jié)
Spring通過(guò)三級(jí)緩存機(jī)制巧妙地解決了單例模式下Setter注入的循環(huán)依賴(lài)問(wèn)題,但其本質(zhì)上是一種妥協(xié)方案。良好的系統(tǒng)設(shè)計(jì)應(yīng)當(dāng)盡量避免循環(huán)依賴(lài),保持清晰的依賴(lài)關(guān)系。理解Spring解決循環(huán)依賴(lài)的機(jī)制,有助于我們更好地使用Spring框架,并在遇到相關(guān)問(wèn)題時(shí)能夠快速定位和解決。
到此這篇關(guān)于深入剖析Spring如何解決循環(huán)依賴(lài)的文章就介紹到這了,更多相關(guān)Spring解決循環(huán)依賴(lài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Java實(shí)現(xiàn)的k-means聚類(lèi)算法
這篇文章主要介紹了詳解Java實(shí)現(xiàn)的k-means聚類(lèi)算法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
Java攔截器Interceptor和過(guò)濾器Filte的執(zhí)行順序和區(qū)別
本文主要介紹了Java攔截器Interceptor和過(guò)濾器Filte的執(zhí)行順序和區(qū)別,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
IntelliJ IDEA使用git初始化倉(cāng)庫(kù)的使用方法
這篇文章主要介紹了IntelliJ IDEA使用git初始化倉(cāng)庫(kù)的使用方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-04-04
利用Java實(shí)體bean對(duì)象批量數(shù)據(jù)傳輸處理方案小結(jié)
javabean是對(duì)面向?qū)ο笏枷氲囊环N具體實(shí)施的表現(xiàn),本文重點(diǎn)給大家介紹利用Java實(shí)體bean對(duì)象批量數(shù)據(jù)傳輸處理方案小結(jié),本文通過(guò)兩種方案給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2021-05-05
Java五種方式實(shí)現(xiàn)多線程循環(huán)打印問(wèn)題
本文主要介紹了Java五種方式實(shí)現(xiàn)多線程循環(huán)打印問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
Mybatis通過(guò)攔截器實(shí)現(xiàn)單數(shù)據(jù)源內(nèi)多數(shù)據(jù)庫(kù)切換
這篇文章主要為大家詳細(xì)介紹了Mybatis如何通過(guò)攔截器實(shí)現(xiàn)單數(shù)據(jù)源內(nèi)多數(shù)據(jù)庫(kù)切換,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-12-12
springboot json時(shí)間格式化處理的方法
這篇文章主要介紹了springboot json時(shí)間格式化處理的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
解析Java實(shí)現(xiàn)設(shè)計(jì)模式六大原則之里氏替換原則
里氏替換原則是用來(lái)幫助我們?cè)诶^承關(guān)系中進(jìn)行父子類(lèi)的設(shè)計(jì)。它闡述了有關(guān)繼承的一些原則,也就是什么時(shí)候應(yīng)該使用繼承,什么時(shí)候不應(yīng)該使用繼承,以及其中蘊(yùn)含的原理。它是繼承復(fù)用的基礎(chǔ),反映了基類(lèi)與子類(lèi)之間的關(guān)系,是對(duì)開(kāi)閉原則的補(bǔ)充,對(duì)實(shí)現(xiàn)抽象化具體步驟的規(guī)范2021-06-06

