Spring三級(jí)緩存解決循環(huán)依賴(lài)的解析過(guò)程
一、循環(huán)依賴(lài)場(chǎng)景
假設(shè)存在兩個(gè)Bean的相互依賴(lài):
@Component public class ServiceA { @Autowired private ServiceB serviceB; } @Component public class ServiceB { @Autowired private ServiceA serviceA; }
二、三級(jí)緩存定義
在 DefaultSingletonBeanRegistry
中定義:
// 一級(jí)緩存:完整Bean(K:Bean名稱(chēng) V:實(shí)例化+初始化完成的Bean) private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二級(jí)緩存:早期暴露對(duì)象(K:Bean名稱(chēng) V:未完成屬性注入的原始Bean) private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 三級(jí)緩存:對(duì)象工廠(K:Bean名稱(chēng) 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: 開(kāi)始屬性注入(需要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: 開(kāi)始屬性注入(需要ServiceA)
3. 解決ServiceB對(duì)ServiceA的依賴(lài)
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放入singletonObjects
4. 回溯完成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í)別 | 解決的問(wèn)題 | 典型場(chǎng)景 |
---|---|---|
singletonFactories | 處理AOP代理的延遲生成 | 需要保證代理對(duì)象的單例性 |
earlySingletonObjects | 避免重復(fù)創(chuàng)建早期引用 | 多個(gè)Bean依賴(lài)同一個(gè)未完成初始化的Bean |
singletonObjects | 存儲(chǔ)最終可用Bean | 正常Bean獲取 |
五、設(shè)計(jì)約束與限制
1.僅支持單例作用域
原型(prototype)作用域的Bean無(wú)法解決循環(huán)依賴(lài),因?yàn)镾pring不緩存原型Bean
2.構(gòu)造器注入限制
如果循環(huán)依賴(lài)通過(guò)構(gòu)造器注入發(fā)生,無(wú)法解決(實(shí)例化前就需要完成依賴(lài)注入)
3.異步初始化風(fēng)險(xiǎn)
使用@Async
等方法增強(qiáng)的Bean可能破壞初始化順序
六、調(diào)試技巧
查看緩存狀態(tài)
在 DefaultSingletonBeanRegistry
類(lèi)中設(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)依賴(lài)異常
在配置類(lèi)添加:
@Bean public CircularReferencesBean circularReferencesBean() { return new CircularReferencesBean(circularReferencesBean()); }
七、性能優(yōu)化建議
避免過(guò)度使用循環(huán)依賴(lài)
即使技術(shù)可行,也應(yīng)通過(guò)設(shè)計(jì)模式(如事件驅(qū)動(dòng))解耦
合理使用@Lazy注解
延遲加載非必要依賴(lài):
@Autowired @Lazy private ServiceB serviceB;
監(jiān)控緩存命中率
通過(guò)JMX監(jiān)控 singletonObjects
與 earlySingletonObjects
的比例
總結(jié)
Spring的三級(jí)緩存機(jī)制通過(guò) 提前暴露對(duì)象引用 + 動(dòng)態(tài)代理生成 的協(xié)同設(shè)計(jì),在保證單例性的前提下,優(yōu)雅地解決了循環(huán)依賴(lài)問(wèn)題。理解該機(jī)制需要重點(diǎn)把握Bean生命周期的階段劃分和緩存狀態(tài)的轉(zhuǎn)換邏輯。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring4.0 MVC請(qǐng)求json數(shù)據(jù)報(bào)406錯(cuò)誤的解決方法
這篇文章主要為大家詳細(xì)介紹了Spring4.0 MVC請(qǐng)求json數(shù)據(jù)報(bào)406錯(cuò)誤的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01Windows下apache ant安裝、環(huán)境變量配置教程
這篇文章主要介紹了Windows下apache ant安裝、環(huán)境變量配置教程,ANT的安裝很簡(jiǎn)單,本文同時(shí)講解了驗(yàn)證安裝是否成功的方法和使用方法實(shí)例,需要的朋友可以參考下2015-06-06java實(shí)現(xiàn)滑動(dòng)驗(yàn)證解鎖
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)滑動(dòng)驗(yàn)證解鎖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07重寫(xiě)equals的同時(shí)為何要重寫(xiě)hashCode?
這篇文章主要給大家介紹了關(guān)于重寫(xiě)equals的同時(shí)為何要重寫(xiě)hashCode的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01Java 數(shù)組內(nèi)置函數(shù)toArray詳解
這篇文章主要介紹了Java 數(shù)組內(nèi)置函數(shù)toArray詳解,文本詳細(xì)的講解了toArray底層的代碼和文檔,需要的朋友可以參考下2021-06-06SpringBoot的ResponseEntity類(lèi)返回給前端具體講解
這篇文章主要給大家介紹了關(guān)于SpringBoot的ResponseEntity類(lèi)返回給前端的相關(guān)資料,ResponseEntity是Spring框架中用于封裝HTTP響應(yīng)的類(lèi),可以自定義狀態(tài)碼、響應(yīng)頭和響應(yīng)體,常用于控制器方法中返回特定數(shù)據(jù)的HTTP響應(yīng),需要的朋友可以參考下2024-11-11Spring?Boot?+?EasyExcel?+?SqlServer?進(jìn)行批量處理數(shù)據(jù)的高效方法
在日常開(kāi)發(fā)和工作中,我們可能要根據(jù)用戶上傳的文件做一系列的處理,本篇文章就以Excel表格文件為例,主要介紹了Spring?Boot?+?EasyExcel?+?SqlServer?進(jìn)行批量處理數(shù)據(jù)的高效方法,需要的朋友可以參考下2024-06-06Java 實(shí)現(xiàn)文件批量重命名親測(cè)可用(精簡(jiǎn)版)
本文給大家分享一段自己寫(xiě)的java代碼實(shí)現(xiàn)文件批量重命名,親測(cè)試過(guò)沒(méi)有任何問(wèn)題,大家可以放心使用2016-11-11Spring Security OAuth2實(shí)現(xiàn)使用JWT的示例代碼
這篇文章主要介紹了Spring Security OAuth2實(shí)現(xiàn)使用JWT的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09