Spring如何利用三級(jí)緩存解密解決循環(huán)依賴難題
引言
在Spring框架的日常開發(fā)中,循環(huán)依賴問題如同一個(gè)幽靈,時(shí)不時(shí)困擾著開發(fā)者。當(dāng)Bean A依賴Bean B,而Bean B又依賴Bean A時(shí),傳統(tǒng)的創(chuàng)建流程會(huì)陷入死鎖。本文將深入剖析Spring如何通過三級(jí)緩存機(jī)制破解這一難題,揭示其背后的設(shè)計(jì)智慧。
一、循環(huán)依賴的本質(zhì)問題
循環(huán)依賴的根源在于對(duì)象創(chuàng)建的順序性矛盾:
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB; // 需要ServiceB實(shí)例
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA; // 需要ServiceA實(shí)例
}
這種"雞生蛋還是蛋生雞"的問題,傳統(tǒng)創(chuàng)建流程無法解決。
二、三級(jí)緩存機(jī)制全景解析
Spring通過三級(jí)緩存架構(gòu)破解循環(huán)依賴:
DefaultSingletonBeanRegistry
-singletonObjects: Map<String, Object> // 一級(jí)緩存:成品Bean
-earlySingletonObjects: Map<String, Object> // 二級(jí)緩存:半成品(早期引用)
-singletonFactories: Map<String, ObjectFactory> // 三級(jí)緩存:對(duì)象工廠
各級(jí)緩存的核心職責(zé)
| 緩存級(jí)別 | 存儲(chǔ)內(nèi)容 | 生命周期 | 作用 |
|---|---|---|---|
| 一級(jí)緩存 | 完全初始化的Bean | 應(yīng)用生命周期 | 提供最終產(chǎn)品 |
| 二級(jí)緩存 | 早期引用(半成品) | 被依賴→初始化完成 | 臨時(shí)周轉(zhuǎn) |
| 三級(jí)緩存 | ObjectFactory對(duì)象 | 實(shí)例化→被依賴/初始化完成 | 延遲生成早期引用 |
三、破解循環(huán)依賴的全流程
以經(jīng)典的A→B→A依賴鏈為例:

關(guān)鍵步驟解析
三級(jí)緩存注冊(cè)(步驟2/5):
// 實(shí)例化后立即注冊(cè) addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, bean));
早期引用生成(步驟9-11):
protected Object getEarlyBeanReference(String beanName, Object bean) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
// 動(dòng)態(tài)決策是否創(chuàng)建代理
bean = ((SmartInstantiationAwareBeanPostProcessor) bp)
.getEarlyBeanReference(bean, beanName);
}
}
return bean;
}
緩存狀態(tài)轉(zhuǎn)移(步驟12/16/20):
- 被依賴后從三級(jí)緩存刪除
- 初始化完成后從二級(jí)緩存刪除
- 最終成品存于一級(jí)緩存
四、三級(jí)緩存的設(shè)計(jì)精妙之處
1. 雙重延遲決策機(jī)制
public Object getEarlyBeanReference() {
// 延遲點(diǎn)1:只在被依賴時(shí)觸發(fā)
// 延遲點(diǎn)2:動(dòng)態(tài)決定是否創(chuàng)建代理
return (needsProxy ? createProxy(bean) : bean);
}
優(yōu)勢(shì):避免為不需要代理或未發(fā)生循環(huán)依賴的Bean創(chuàng)建額外對(duì)象
2. 狀態(tài)完整性保障

當(dāng)創(chuàng)建代理時(shí),Bean已通過populateBean()完成屬性注入,避免NPE風(fēng)險(xiǎn)
3. 對(duì)象版本統(tǒng)一性
// 最終代理一致性保證
public void initializeBean() {
if (earlyProxyReference != null) {
return earlyProxyReference; // 復(fù)用已創(chuàng)建的代理
}
return createProxy(bean); // 無循環(huán)依賴時(shí)創(chuàng)建
}
4. 資源高效利用
| 場景 | 傳統(tǒng)方案 | 三級(jí)緩存方案 | 性能提升 |
|---|---|---|---|
| 無循環(huán)依賴 | 創(chuàng)建所有代理 | 不創(chuàng)建代理 | 節(jié)省90%內(nèi)存 |
| 有循環(huán)依賴無代理 | 創(chuàng)建半成品副本 | 直接使用原始對(duì)象 | 減少對(duì)象創(chuàng)建 |
| 有循環(huán)依賴需代理 | 可能創(chuàng)建多個(gè)代理 | 單例代理 | 避免代理沖突 |
五、疑難場景解決方案
1. 代理對(duì)象循環(huán)依賴
@Service
public class UserService {
@Autowired
private OrderService orderService;
@Transactional // 需要代理
public void createUser() {...}
}
解決方案:
- 在
getEarlyBeanReference()中創(chuàng)建代理 - 保證代理對(duì)象基于完成屬性注入的狀態(tài)
2. 多級(jí)循環(huán)依賴
A→B→C→A依賴鏈:

處理流程:
- C獲取A時(shí)觸發(fā)三級(jí)緩存
- 返回A的早期引用
- C完成初始化
- B獲得C的引用
- A最終獲得B的引用
3. 無法解決的場景
| 場景 | 原因 |
|---|---|
| 構(gòu)造器循環(huán)依賴 | 對(duì)象未實(shí)例化完成,無法暴露引用 |
| 原型(Prototype)作用域 | Spring不緩存原型Bean |
| @Async方法 | 代理生成時(shí)機(jī)與標(biāo)準(zhǔn)AOP不同 |
六、性能優(yōu)化建議
避免循環(huán)依賴:重構(gòu)設(shè)計(jì),引入事件機(jī)制
// 使用事件解耦 applicationContext.publishEvent(new UserCreatedEvent(user));
懶加載優(yōu)化:
@Lazy @Autowired private HeavyService heavyService; // 延遲初始化
作用域控制:
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedBean {...}
結(jié)論
Spring的三級(jí)緩存機(jī)制通過以下創(chuàng)新設(shè)計(jì)解決循環(huán)依賴:
- 空間換時(shí)間:通過三級(jí)緩存狀態(tài)管理打破創(chuàng)建順序限制
- 延遲決策:在被依賴時(shí)才決定是否創(chuàng)建代理
- 狀態(tài)保障:確保代理對(duì)象基于完整初始化狀態(tài)
- 資源優(yōu)化:避免不必要的對(duì)象創(chuàng)建
理解三級(jí)緩存不僅幫助解決循環(huán)依賴異常,更是深入掌握Spring框架設(shè)計(jì)思想的鑰匙。正如Spring框架創(chuàng)始人Rod Johnson所說:"好的框架設(shè)計(jì)是在約束與靈活性之間找到完美平衡",三級(jí)緩存正是這種平衡的藝術(shù)體現(xiàn)。
到此這篇關(guān)于Spring如何利用三級(jí)緩存解密解決循環(huán)依賴難題的文章就介紹到這了,更多相關(guān)Spring三級(jí)緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java界面編程實(shí)現(xiàn)界面跳轉(zhuǎn)
這篇文章主要為大家詳細(xì)介紹了Java界面編程實(shí)現(xiàn)界面跳轉(zhuǎn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
解決程序啟動(dòng)報(bào)錯(cuò)org.springframework.context.ApplicationContextExcept
文章描述了一個(gè)Spring Boot項(xiàng)目在不同環(huán)境下啟動(dòng)時(shí)出現(xiàn)差異的問題,通過分析報(bào)錯(cuò)信息,發(fā)現(xiàn)是由于導(dǎo)入`spring-boot-starter-tomcat`依賴時(shí)定義的scope導(dǎo)致的配置問題,調(diào)整依賴導(dǎo)入配置后,解決了啟動(dòng)錯(cuò)誤2024-11-11
Mybatis 查詢語句條件為枚舉類型時(shí)報(bào)錯(cuò)的解決
這篇文章主要介紹了Mybatis 查詢語句條件為枚舉類型時(shí)報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
利用Java實(shí)現(xiàn)天氣預(yù)報(bào)播報(bào)功能
這篇文章主要為大家介紹了如何利用Java語言實(shí)現(xiàn)天氣預(yù)報(bào)播報(bào)功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定的幫助,需要的可以參考一下2022-06-06
Mybatis查詢方法如何實(shí)現(xiàn)沒有返回值
這篇文章主要介紹了Mybatis查詢方法如何實(shí)現(xiàn)沒有返回值,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
詳解Spring Cloud Zuul中路由配置細(xì)節(jié)
本篇文章主要介紹了詳解Spring Cloud Zuul中路由配置細(xì)節(jié),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10
手把手教你使用IDEA創(chuàng)建多模塊(maven)項(xiàng)目
這篇文章主要給大家介紹了關(guān)于如何使用IDEA創(chuàng)建多模塊(maven)項(xiàng)目的相關(guān)資料,文中通過圖文以及實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07

