Spring如何利用三級(jí)緩存解密解決循環(huán)依賴難題
引言
在Spring框架的日常開(kāi)發(fā)中,循環(huán)依賴問(wèn)題如同一個(gè)幽靈,時(shí)不時(shí)困擾著開(kāi)發(fā)者。當(dāng)Bean A依賴Bean B,而B(niǎo)ean B又依賴Bean A時(shí),傳統(tǒng)的創(chuàng)建流程會(huì)陷入死鎖。本文將深入剖析Spring如何通過(guò)三級(jí)緩存機(jī)制破解這一難題,揭示其背后的設(shè)計(jì)智慧。
一、循環(huán)依賴的本質(zhì)問(wèn)題
循環(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í)例
}
這種"雞生蛋還是蛋生雞"的問(wèn)題,傳統(tǒng)創(chuàng)建流程無(wú)法解決。
二、三級(jí)緩存機(jī)制全景解析
Spring通過(guò)三級(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已通過(guò)populateBean()完成屬性注入,避免NPE風(fēng)險(xiǎn)
3. 對(duì)象版本統(tǒng)一性
// 最終代理一致性保證
public void initializeBean() {
if (earlyProxyReference != null) {
return earlyProxyReference; // 復(fù)用已創(chuàng)建的代理
}
return createProxy(bean); // 無(wú)循環(huán)依賴時(shí)創(chuàng)建
}
4. 資源高效利用
| 場(chǎng)景 | 傳統(tǒng)方案 | 三級(jí)緩存方案 | 性能提升 |
|---|---|---|---|
| 無(wú)循環(huán)依賴 | 創(chuàng)建所有代理 | 不創(chuàng)建代理 | 節(jié)省90%內(nèi)存 |
| 有循環(huán)依賴無(wú)代理 | 創(chuàng)建半成品副本 | 直接使用原始對(duì)象 | 減少對(duì)象創(chuàng)建 |
| 有循環(huán)依賴需代理 | 可能創(chuàng)建多個(gè)代理 | 單例代理 | 避免代理沖突 |
五、疑難場(chǎng)景解決方案
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. 無(wú)法解決的場(chǎng)景
| 場(chǎng)景 | 原因 |
|---|---|
| 構(gòu)造器循環(huán)依賴 | 對(duì)象未實(shí)例化完成,無(wú)法暴露引用 |
| 原型(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ī)制通過(guò)以下創(chuàng)新設(shè)計(jì)解決循環(huán)依賴:
- 空間換時(shí)間:通過(guò)三級(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所說(shuō):"好的框架設(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 charAt()返回?cái)?shù)值型的使用示例
本文主要介紹了java charAt()返回?cái)?shù)值型的使用示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11
Springboot整合企業(yè)微信機(jī)器人助手推送消息的實(shí)現(xiàn)
本文主要介紹了Springboot整合企業(yè)微信機(jī)器人助手推送消息的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
Java使用easypoi快速導(dǎo)入導(dǎo)出的實(shí)現(xiàn)
這篇文章主要介紹了實(shí)現(xiàn)Java使用easypoi快速導(dǎo)入導(dǎo)出的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03
java中ConcurrentHashMap的讀操作為什么不需要加鎖
ConcurrentHashMap完全允許多個(gè)讀操作并發(fā)進(jìn)行,讀操作并不需要加鎖。所以下面這篇文章主要給大家介紹了關(guān)于java中ConcurrentHashMap的讀操作為什么不需要加鎖的相關(guān)資料,需要的朋友可以參考下2018-10-10

