Spring源碼之解決循環依賴 三級緩存

目錄

三級緩存核心原理

循環依賴的解決過程

1. Bean A創建過程中提前曝光工廠

2. Bean B創建時發現依賴A,從緩存獲取

3. Bean A繼續完成初始化

三級緩存的作用總結

二級緩存為何不夠解決緩存依賴?

三級緩存如何解決?

為什么不直接在實例化后創建代理?

總結


Spring通過三級緩存機制解決循環依賴問題,這與populateBean()方法密切相關。下面我用簡化代碼解釋這個機制:

三級緩存核心原理

Spring在DefaultSingletonBeanRegistry中維護了三個重要的緩存:

// 一級緩存:存儲完全初始化的單例bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 二級緩存:存儲早期曝光的單例bean(未完全初始化)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// 三級緩存:存儲單例工廠對象,用于創建早期曝光的bean
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

循環依賴的解決過程

當發生循環依賴時,Spring通過以下步驟解決:

1. Bean A創建過程中提前曝光工廠
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {// 1. 實例化Bean(調用構造函數)BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);// 2. 提前曝光一個ObjectFactory,用于解決循環依賴boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences);if (earlySingletonExposure) {addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// 3. 填充屬性(可能觸發對Bean B的依賴)populateBean(beanName, mbd, instanceWrapper);// 4. 初始化Bean(調用init方法和AOP代理)exposedObject = initializeBean(beanName, exposedObject, mbd);return exposedObject;
}// 將工廠添加到三級緩存
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);}}
}
2. Bean B創建時發現依賴A,從緩存獲取

populateBean()處理B的依賴時:

protected Object resolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {// 查找依賴的bean(假設這里是A)String dependencyName = descriptor.getDependencyName();// 從三級緩存中獲取beanObject bean = getSingleton(dependencyName, false);return bean;
}// 從三級緩存中獲取bean的核心方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 先從一級緩存獲取Object singletonObject = this.singletonObjects.get(beanName);// 2. 如果一級緩存沒有且bean正在創建中if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 3. 從二級緩存獲取singletonObject = this.earlySingletonObjects.get(beanName);// 4. 二級緩存沒有且允許早期引用if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// 5. 從三級緩存獲取工廠ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 6. 通過工廠創建早期bean實例singletonObject = singletonFactory.getObject();// 7. 放入二級緩存this.earlySingletonObjects.put(beanName, singletonObject);// 8. 從三級緩存移除this.singletonFactories.remove(beanName);}}}}return singletonObject;
}
3. Bean A繼續完成初始化

當B完成創建后,A繼續完成自己的初始化:

// 完成Bean A的初始化
exposedObject = initializeBean(beanName, exposedObject, mbd);// 將完全初始化的bean放入一級緩存
addSingleton(beanName, exposedObject);protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {this.singletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);this.earlySingletonObjects.remove(beanName);}
}

三級緩存的作用總結

  1. 一級緩存(singletonObjects):存儲完全初始化的單例bean,可直接使用
  2. 二級緩存(earlySingletonObjects):存儲早期曝光的bean(未完成初始化),用于解決循環依賴
  3. 三級緩存(singletonFactories):存儲工廠對象,允許在需要時創建早期bean,并支持AOP代理

通過這種機制,Spring允許在bean尚未完全初始化時就將其引用暴露給依賴它的其他bean,從而解決循環依賴問題。注意:只有單例作用域的bean支持循環依賴,原型模式不支持。你的問題非常關鍵!實際上,二級緩存理論上可以解決循環依賴,但Spring依然使用三級緩存,主要出于AOP代理的考慮。我來詳細解釋:

二級緩存為何不夠解決緩存依賴?

假設只有兩級緩存:

  1. singletonObjects(完全初始化的bean)
  2. earlySingletonObjects(早期曝光的bean)

當出現循環依賴時,我們可以在實例化后直接將bean放入earlySingletonObjects。但這樣會有一個問題:如果bean需要AOP代理,早期曝光的將是原始對象,而不是代理對象

舉個例子:

// Bean A依賴B,Bean B依賴A
class A {@Autowired private B b;@Transactional public void doSomething() {} // 需要AOP代理
}class B {@Autowired private A a;
}

當A和B循環依賴時:

  1. A實例化后放入二級緩存(此時是原始對象)
  2. B創建時從二級緩存獲取A的原始對象
  3. B完成創建,注入A的原始對象
  4. A繼續初始化,創建代理對象

最終結果:B持有的是A的原始對象,而不是代理對象,導致AOP失效!

三級緩存如何解決?

Spring通過三級緩存引入了ObjectFactory,將AOP代理的創建延遲到真正需要早期引用時:

// 三級緩存:存儲工廠對象,用于創建可能需要代理的早期bean
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);// 添加工廠到三級緩存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));// 獲取早期引用的方法(可能創建代理)
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (BeanPostProcessor bp : getBeanPostProcessors()) {if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;// 如果需要代理,這里會創建代理對象exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);}}}return exposedObject;
}

當B需要A的早期引用時:

  1. 從三級緩存獲取ObjectFactory
  2. 通過工廠調用getEarlyBeanReference(),此時才決定是否創建代理
  3. 將結果(可能是代理對象)放入二級緩存

這樣一來,B持有的就是A的代理對象,保證了AOP的正確性。

為什么不直接在實例化后創建代理?

你可能會問:為什么不直接在實例化A后就創建代理,然后放入二級緩存?這樣不就不需要三級緩存了嗎?

原因有兩點:

  1. 性能優化:不是所有bean都需要代理,延遲到真正需要時再創建可以避免不必要的代理
  2. 順序正確性:Spring的后置處理器執行順序是有規范的。postProcessBeforeInitializationpostProcessAfterInitialization應該在bean初始化階段執行,而不是實例化階段。如果提前創建代理,會破壞這個順序。

總結

二級緩存可以解決普通對象的循環依賴,但無法解決代理對象的循環依賴。三級緩存通過引入ObjectFactory,將代理創建延遲到真正需要早期引用時,既保證了AOP的正確性,又維持了后置處理器的執行順序。這是Spring在循環依賴和AOP之間找到的精妙平衡點。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/80293.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/80293.shtml
英文地址,請注明出處:http://en.pswp.cn/web/80293.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

K8S Ingress 實現AB測試、藍綠發布、金絲雀(灰度)發布

假設有如下三個節點的 K8S 集群&#xff1a; ? k8s31master 是控制節點 k8s31node1、k8s31node2 是工作節點 容器運行時是 containerd 一、場景分析 閱讀本文&#xff0c;默認您已經安裝了 Ingress Nginx。 1&#xff09;A/B 測試 A/B 測試基于用戶請求的元信息將流量路由…

深入理解構造函數,析構函數

目錄 1.引言 2.構造函數 1.概念 2.特性 3.析構函數 1.概念 2.特性 1.引言 如果一個類中什么都沒有&#xff0c;叫作空類. class A {}; 那么我們這個類中真的是什么都沒有嗎?其實不是,如果我們類當中上面都不寫.編譯器會生成6個默認的成員函數。 默認成員函數:用戶沒有顯…

Oracle 11.2.0.4 pre PSU Oct18 設置SSL連接

Oracle 11.2.0.4 pre PSU Oct18 設置SSL連接 1 說明2 客戶端配置jdk環境3服務器檢查oracle數據庫補丁4設置ssla 服務器配置walletb 上傳測試腳本和配置文件到客戶端c 服務器修改數據庫偵聽和sqlnet.orad 修改客戶端的sqlnet.ora和tnsnames.ora的連接符e 修改java代碼的數據連接…

BrepGen中的幾何特征組裝與文件保存詳解 deepwiki occwl OCC包裝庫

有這種好東西我怎么不知道 AutodeskAILab/occwl: Lightweight Pythonic wrapper around pythonocc 組裝幾何特征以創建B-rep模型 保存為STEP和STL文件細說 Fast 快速 Searched across samxuxiang/BrepGen Ill explain how BrepGen assembles geometric features to create B-r…

重慶 ICPC 比賽游記

2025.5.9 比賽前一天晚上&#xff0c;激動地睡不著覺&#xff0c;起來收拾了好多東西。&#xff08;其實就四本書&#xff0c;剩下的全是零食……關鍵在于這四本書基本沒用。&#xff09; 2025.5.10 學校喪心病狂的讓我們 6:20 到校門口集合坐車&#xff08;據說是怕趕不上比…

0x08.Redis 支持事務嗎?如何實現?

回答重點 Redis 支持事務,但它的事務與 MySQL 等關系型數據庫的事務有著本質區別。MySQL 中的事務嚴格遵循 ACID 特性,而 Redis 中的事務主要保證的是命令執行的原子性和隔離性,即所有命令在一個不可分割的操作中順序執行,不會被其他客戶端的命令請求所打斷。 最關鍵的區…

佰力博科技與您探討表面電阻的測試方法及應用領域

表面電阻測試是一種用于測量材料表面電阻值的技術&#xff0c;廣泛應用于評估材料的導電性能、靜電防護性能以及絕緣性能。 1、表面電阻的測試測試方法&#xff1a; 表面電阻測試通常采用平行電極法、同心圓電極法和四探針法等方法進行。其中&#xff0c;平行電極法通過在試樣…

數據庫的規范化設計方法---3種范式

第一范式&#xff08;1NF&#xff09;&#xff1a;確保表中的每個字段都是不可分割的基本數據項。 第二范式&#xff08;2NF&#xff09;&#xff1a;在滿足1NF的基礎上&#xff0c;確保非主屬性完全依賴于主鍵。 第三范式&#xff08;3NF&#xff09;&#xff1a;在滿足2NF的基…

產品經理入門(2)產品體驗報告

產品體驗報告大綱&#xff1a;重點在產品體驗——優點。 1.產品概括 可以從各大平臺搜產品介紹。 2.市場分析 按照產品方向分析各個指標——包括有效使用時間,市場規模等。 3. 用戶分析——對用戶通過各項指標畫像。 4.產品體驗——對各項功能與設計的體驗。 5.報告總結

[Java][Leetcode simple] 13. 羅馬數字轉整數

一、自己想的 只有提到的六種情況是-&#xff0c;其他都是 public int romanToInt1(String s) {int res 0;int n s.length();Map<Character, Integer> map new HashMap<>();map.put(I, 1);map.put(V, 5);map.put(X, 10);map.put(L, 50);map.put(C, 100);map.pu…

如何在 CentOS 7 虛擬機上配置靜態 IP 地址并保持重啟后 SSH 連接

在使用 CentOS 7 的虛擬機時&#xff0c;我們通常需要配置靜態 IP 地址&#xff0c;以確保在每次虛擬機重啟后能夠通過 SSH 連接。本文將介紹如何在 CentOS 7 系統中配置靜態 IP 地址&#xff0c;并確保配置在系統重啟后依然生效。 步驟 1&#xff1a;檢查虛擬機網絡接口 首先…

matlab求解問題

一、目的 掌握Matlab中函數求導、函數極值和極限問題的求解,能夠借助Matlab工具對簡單優化模型進行求解。 二、內容與設計思想 1、函數求導 1.1求解給定函數的一階導數&#xff1a;diff(y, x)用于對變量x求y的導數。 1.2求解給定函數的二階導數&#xff1a;在求出一階導數的…

C語言斐波那契數列

斐波那契數列&#xff08;Fibonacci sequence&#xff09;&#xff0c;又稱黃金分割數列 、兔子數列。由意大利數學家萊昂納多?斐波那契在 1202 年提出&#xff0c;源于其《算盤書》中一道兔子繁殖問題。定義&#xff1a;在數學上&#xff0c;該數列以遞歸形式定義。最常見的是…

AI浪潮:開啟科技新紀元

AI 的多面應用? AI 的影響力早已突破實驗室的圍墻&#xff0c;在眾多領域落地生根&#xff0c;成為推動行業變革的重要力量。 在醫療領域&#xff0c;AI 宛如一位不知疲倦的助手&#xff0c;助力醫生提升診療效率與準確性。通過對海量醫學影像的深度學習&#xff0c;AI 能夠快…

Ansys 計算剛柔耦合矩陣系數

Ansys 計算剛柔耦合系數矩陣 文章目錄 Ansys 計算剛柔耦合系數矩陣衛星的剛柔耦合動力學模型采用 ANSYS 的 APDL 語言的計算方法系統轉動慣量的求解方法參考文獻 衛星的剛柔耦合動力學模型 柔性航天器的剛柔耦合動力學模型可以表示為 m v ˙ B t r a n η F J ω ˙ ω J…

算法題(148):排座椅

審題&#xff1a; 本題需要我們找到最佳的排座椅方案&#xff0c;并輸出行&#xff0c;列方案 思路&#xff1a; 方法一&#xff1a;簡單貪心 由于題目會告訴我們有哪幾對的同學會交頭接耳&#xff0c;所以我們可以記錄下第幾行/第幾列上可以隔開的同學對數&#xff0c;而題目限…

企業級電商數據對接:1688 商品詳情 API 接口開發與優化實踐

在數字化浪潮席卷全球的當下&#xff0c;企業級電商平臺之間的數據對接已成為提升運營效率、增強市場競爭力的關鍵環節。作為國內知名的 B2B 電商平臺&#xff0c;1688 擁有海量商品資源&#xff0c;通過開發和優化商品詳情 API 接口&#xff0c;企業能夠快速獲取商品信息&…

【Cesium入門教程】第七課:Primitive圖元

Cesium豐富的空間數據可視化API分為兩部分&#xff1a;primitive API面向三維圖形開發者&#xff0c;更底層一些。 Entity API是數據驅動更高級一些。 // entity // 調用方便&#xff0c;封裝完美 // 是基于primitive的封裝// primitive // 更接近底層 // 可以繪制高級圖形 /…

Oracle APEX 必須輸入項目標簽型號顯示位置

1. 正常Oracle APEX中必須輸入項目標簽的紅星顯示在標簽文字左側&#xff0c;偏偏項目要求顯示在右側&#xff0c; 加入如下全局CSS代碼 .t-Form-label {display: flex;flex-direction: row-reverse;gap: 1px; }以上。

深入理解 TypeScript 中的 unknown 類型:安全處理未知數據的最佳實踐

在 TypeScript 的類型體系中&#xff0c;unknown 是一個極具特色的類型。它與 any 看似相似&#xff0c;卻在安全性上有著本質差異。本文將從設計理念、核心特性、使用場景及最佳實踐等方面深入剖析 unknown&#xff0c;幫助開發者在處理動態數據時既能保持靈活性&#xff0c;又…