Spring三級(jí)緩存解決循環(huán)依賴的解析過程
一、循環(huán)依賴場(chǎng)景
假設(shè)存在兩個(gè)Bean的相互依賴:
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA;
}二、三級(jí)緩存定義
在 DefaultSingletonBeanRegistry 中定義:
// 一級(jí)緩存:完整Bean(K:Bean名稱 V:實(shí)例化+初始化完成的Bean) private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二級(jí)緩存:早期暴露對(duì)象(K:Bean名稱 V:未完成屬性注入的原始Bean) private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 三級(jí)緩存:對(duì)象工廠(K:Bean名稱 V:ObjectFactory) private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
三、解決流程(以ServiceA和ServiceB為例)
1. 創(chuàng)建ServiceA
sequenceDiagram
participant Container
participant Cache1 as singletonObjects
participant Cache2 as earlySingletonObjects
participant Cache3 as singletonFactories
Container->>Cache1: 檢查ServiceA是否存在
Cache1-->>Container: 不存在
Container->>Container: 實(shí)例化ServiceA(構(gòu)造器調(diào)用)
Container->>Cache3: 添加ServiceA的ObjectFactory
Container->>Container: 開始屬性注入(需要ServiceB)關(guān)鍵代碼段:
// AbstractAutowireCapableBeanFactory#doCreateBean addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
2. 發(fā)現(xiàn)需要ServiceB
sequenceDiagram
participant Container
participant Cache1
participant Cache2
participant Cache3
Container->>Cache1: 檢查ServiceB是否存在
Cache1-->>Container: 不存在
Container->>Container: 實(shí)例化ServiceB(構(gòu)造器調(diào)用)
Container->>Cache3: 添加ServiceB的ObjectFactory
Container->>Container: 開始屬性注入(需要ServiceA)3. 解決ServiceB對(duì)ServiceA的依賴
sequenceDiagram
participant Container
participant Cache1
participant Cache2
participant Cache3
Container->>Cache1: 查找ServiceA
Cache1-->>Container: 不存在
Container->>Cache2: 查找ServiceA
Cache2-->>Container: 不存在
Container->>Cache3: 獲取ServiceA的ObjectFactory
Container->>Container: 執(zhí)行g(shù)etEarlyBeanReference()
Container->>Cache2: 將生成的代理對(duì)象存入earlySingletonObjects
Container->>ServiceB: 注入ServiceA的早期引用
Container->>Container: 完成ServiceB初始化
Container->>Cache1: 將ServiceB放入singletonObjects4. 回溯完成ServiceA初始化
sequenceDiagram
participant Container
participant Cache1
participant Cache2
participant Cache3
Container->>ServiceA: 注入已初始化的ServiceB
Container->>Container: 執(zhí)行ServiceA的初始化后方法
Container->>Cache1: 將ServiceA放入singletonObjects
Container->>Cache2: 移除ServiceA的早期引用
Container->>Cache3: 移除ServiceA的ObjectFactory四、關(guān)鍵機(jī)制詳解
1. getEarlyBeanReference() 的核心作用
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
// 如果有必要,在此處生成代理對(duì)象
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp =
(SmartInstantiationAwareBeanPostProcessor) bp;
bean = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return bean;
}2. 三級(jí)緩存必要性分析
| 緩存級(jí)別 | 解決的問題 | 典型場(chǎng)景 |
|---|---|---|
| singletonFactories | 處理AOP代理的延遲生成 | 需要保證代理對(duì)象的單例性 |
| earlySingletonObjects | 避免重復(fù)創(chuàng)建早期引用 | 多個(gè)Bean依賴同一個(gè)未完成初始化的Bean |
| singletonObjects | 存儲(chǔ)最終可用Bean | 正常Bean獲取 |
五、設(shè)計(jì)約束與限制
1.僅支持單例作用域
原型(prototype)作用域的Bean無法解決循環(huán)依賴,因?yàn)镾pring不緩存原型Bean
2.構(gòu)造器注入限制
如果循環(huán)依賴通過構(gòu)造器注入發(fā)生,無法解決(實(shí)例化前就需要完成依賴注入)
3.異步初始化風(fēng)險(xiǎn)
使用@Async等方法增強(qiáng)的Bean可能破壞初始化順序
六、調(diào)試技巧
查看緩存狀態(tài)
在 DefaultSingletonBeanRegistry 類中設(shè)置斷點(diǎn):
// 查看三級(jí)緩存內(nèi)容
System.out.println("singletonFactories: " + singletonFactories.keySet());
System.out.println("earlySingletonObjects: " + earlySingletonObjects.keySet());
System.out.println("singletonObjects: " + singletonObjects.keySet());強(qiáng)制拋出循環(huán)依賴異常
在配置類添加:
@Bean
public CircularReferencesBean circularReferencesBean() {
return new CircularReferencesBean(circularReferencesBean());
}七、性能優(yōu)化建議
避免過度使用循環(huán)依賴
即使技術(shù)可行,也應(yīng)通過設(shè)計(jì)模式(如事件驅(qū)動(dòng))解耦
合理使用@Lazy注解
延遲加載非必要依賴:
@Autowired @Lazy private ServiceB serviceB;
監(jiān)控緩存命中率
通過JMX監(jiān)控 singletonObjects 與 earlySingletonObjects 的比例
總結(jié)
Spring的三級(jí)緩存機(jī)制通過 提前暴露對(duì)象引用 + 動(dòng)態(tài)代理生成 的協(xié)同設(shè)計(jì),在保證單例性的前提下,優(yōu)雅地解決了循環(huán)依賴問題。理解該機(jī)制需要重點(diǎn)把握Bean生命周期的階段劃分和緩存狀態(tài)的轉(zhuǎn)換邏輯。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)的2048游戲完整實(shí)例
這篇文章主要介紹了java實(shí)現(xiàn)的2048游戲,結(jié)合完整實(shí)例形式分析了java實(shí)現(xiàn)2048游戲功能的相關(guān)數(shù)值運(yùn)算、swing組件布局、事件響應(yīng)等相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
使用Java實(shí)現(xiàn)對(duì)兩個(gè)秒級(jí)時(shí)間戳相加
在現(xiàn)代應(yīng)用程序開發(fā)中,時(shí)間戳的處理是一個(gè)常見需求,特別是當(dāng)我們需要對(duì)時(shí)間戳進(jìn)行運(yùn)算時(shí),比如時(shí)間戳的相加操作,本文我們將探討如何使用Java對(duì)兩個(gè)秒級(jí)時(shí)間戳進(jìn)行相加,并展示詳細(xì)的代碼示例和運(yùn)行結(jié)果,需要的朋友可以參考下2024-08-08
源碼解析Spring 數(shù)據(jù)庫異常抽理知識(shí)點(diǎn)總結(jié)
在本篇文章里小編給大家分享了關(guān)于源碼解析Spring 數(shù)據(jù)庫異常抽理知識(shí)點(diǎn)內(nèi)容,對(duì)此有需要的朋友們學(xué)習(xí)參考下。2019-05-05
Mybatis基于注解形式的sql語句生成實(shí)例代碼
這篇文章主要介紹了 Mybatis基于注解形式的sql語句生成實(shí)例代碼,需要的朋友可以參考下2017-09-09
SSM項(xiàng)目中使用攔截器和過濾器的實(shí)現(xiàn)示例
這篇文章主要介紹了SSM項(xiàng)目中使用攔截器和過濾器的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04

