Spring如何巧妙解決循環依賴問題

什么是循環依賴?

循環依賴是指兩個或多個Bean之間相互依賴,形成閉環的情況。例如:AService依賴BService,而BService又依賴AService。這種場景下,傳統的創建順序無法滿足依賴注入的要求。

Spring的三級緩存機制

Spring通過三級緩存機制優雅地解決了循環依賴問題:

1. 第一級緩存:singletonObjects

  • 存儲完全初始化完成的單例Bean

  • 這里的Bean是經過完整生命周期處理的(包括AOP代理)

2. 第二級緩存:earlySingletonObjects

  • 存儲提前暴露的Bean引用(早期引用)

  • 這些Bean尚未完成完整初始化,但可以用于解決循環依賴

3. 第三級緩存:singletonFactories

  • 存儲Bean的ObjectFactory(工廠對象)

  • 通過工廠可以獲取Bean的早期引用,必要時創建代理對象

解決循環依賴的詳細流程

場景:AService ←→ BService 相互依賴

java

// AService 依賴 BService
@Service
public class AService {@Autowiredprivate BService bService;
}// BService 依賴 AService
@Service
public class BService {@Autowiredprivate AService aService;
}

解決步驟:

  1. 開始創建AService

    • creatingSet.add('AService')?- 標記AService正在創建

    • 實例化AService普通對象

  2. AService需要注入BService

    • 從單例池獲取BService,不存在則開始創建BService

  3. 創建BService

    • creatingSet.add('BService')?- 標記BService正在創建

    • 實例化BService普通對象

    • BService需要注入AService

  4. BService獲取AService

    • 從單例池獲取AService,不存在

    • 檢查creatingSet發現AService正在創建(循環依賴 detected!)

    • 從三級緩存獲取AService的ObjectFactory

    • 執行工廠方法:getEarlyBeanReference()?→ 必要時創建AService代理對象

    • 將結果存入二級緩存earlySingletonObjects,并從三級緩存移除工廠

  5. BService繼續初始化

    • 注入AService的早期引用(可能是代理對象)

    • 完成其他屬性填充

    • 執行AOP(如果需要)

    • 將完整BService存入單例池

  6. AService繼續初始化

    • 注入已創建的BService

    • 完成其他屬性填充

    • 執行AOP(但發現已提前AOP,直接使用早期引用)

    • 將完整AService存入單例池

關鍵源碼分析

1. 添加單例工廠

java

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {// 添加到三級緩存this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}
}

doCreateBean()方法中,Spring會提前暴露Bean的工廠:

java

// 為了解決循環依賴提前緩存單例創建工廠
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

2. 獲取單例Bean的邏輯

java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 1. 從一級緩存查找Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 2. 從二級緩存查找singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// 3. 從三級緩存查找并創建早期引用ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();// 移動到二級緩存this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}

3. 提前AOP處理

java

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {// 如果需要AOP,則創建代理對象Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));return proxy;}return bean;
}

為什么需要三級緩存?

  1. 一級緩存:存儲完整的單例Bean,保證單例性質

  2. 二級緩存:存儲早期引用,避免多次執行AOP產生多個代理對象

  3. 三級緩存:存儲工廠對象,延遲決策是否需要進行AOP

如果只有二級緩存,每次獲取早期引用都需要執行AOP,可能導致創建多個不同的代理對象,違反單例原則。

使用注意事項

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

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

相關文章

CUDA 中Thrust exclusive_scan使用詳解

1. 基本概念Thrust 是 NVIDIA CUDA 提供的類似 C STL 的并行算法庫。Scan (前綴和)&#xff1a;給定數組 [a0, a1, a2, ...]&#xff0c;產生前綴和序列。Exclusive Scan (排他前綴和)&#xff1a; 輸出位置 i 存放的是輸入數組中 0 到 i-1 的累積結果。換句話說&#xff0c;結…

Linux -- 信號【上】

目錄 一、信號的引入 1、信號概念 2、signal函數 普通標準信號詳解表 3、前臺/后臺進程 3.1 概念 3.2 查看后臺進程 3.3 后臺進程拉回前臺 3.4 終止后臺進程 3.5 暫停前臺進程 3.6 回復運行后臺進程 4、發信號的本質 二、信號的產生 1、終端按鍵 2、系統調用 2…

Altium Designer(AD)自定義PCB外觀顏色

目錄 1視圖設置界面介紹 2PCB阻焊層顏色設置 2.1進入視圖設置界面 2.2阻焊層顏色設置 2.3頂層和底層阻焊層顏色設置 2.4頂層阻焊層試圖效果 2.5底層阻焊層試圖效果 3設置PCB絲印顏色設置 3.1找到絲印設置選項 3.2設置頂層和底層絲印顏色 3.3頂層絲印 3.4底層絲印 4…

5天改造,節能50%!冷能改造如何實現“不停產節能”?

你有沒有發現一個現象&#xff1f;很多工廠老板一提到節能改造&#xff0c;第一反應就是搖頭。不是不想省電費&#xff0c;而是怕停產。停產一天損失幾十萬&#xff0c;改造周期動輒幾個月&#xff0c;這賬怎么算都不劃算。但如果我告訴你&#xff0c;有一種改造方式&#xff0…

【Flink】窗口

目錄窗口窗口的概念窗口的分類滾動窗口&#xff08;Tumbling Windows&#xff09;滑動窗口&#xff08;Sliding Windows&#xff09;會話窗口&#xff08;Session Windows&#xff09;全局窗口&#xff08;Global Windows&#xff09;窗口API概覽窗口函數增量聚合函數ReduceFun…

攻擊路徑(4):API安全風險導致敏感數據泄漏

本文是《攻防演練 | JS泄露到主機失陷[1]》的學習筆記&#xff0c;歡迎大家閱讀原文。攻擊路徑通過未授權訪問攻擊獲取敏感數據通過SQL注入攻擊獲取服務器權限通過憑據訪問攻擊獲取數據庫權限和敏感數據和應用權限安全風險與加固措施通過未授權訪問攻擊獲取敏感數據、通過SQL注…

機器學習面試題:請介紹一下你理解的集成學習算法

集成學習&#xff08;Ensemble Learning&#xff09;的核心思想是“集思廣益”&#xff0c;它通過構建并結合多個基學習器&#xff08;Base Learner&#xff09;來完成學習任務&#xff0c;從而獲得比單一學習器更顯著優越的泛化性能。俗話說&#xff0c;“三個臭皮匠&#xff…

Invalid bound statement (not found): com.XXX.XXx.service.xxx無法執行service

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.xxx.xxx.service.CitytownService.selectCitytown 出現無法加載sevice層的時候&#xff0c;如下圖所示1&#xff0c;處理方法是&#xff0c;先看下注解MapperScan內的包地址&#xff0c…

泛型(Generics)what why when【前端TS】

我總是提醒自己一定要嚴謹嚴謹嚴謹 目錄TypeScript 泛型 (Generics)1. 什么是泛型&#xff1f;2. 為什么需要泛型&#xff1f;3. 泛型常見用法3.1 函數泛型3.2 接口泛型3.3 類泛型3.4 泛型約束3.5 泛型默認值3.6 多個泛型參數4. 泛型應用場景TypeScript 泛型 (Generics) 1. 什…

分布式協議與算法實戰-協議和算法篇

05丨Paxos算法&#xff08;一&#xff09;&#xff1a;如何在多個節點間確定某變量的值? 提到分布式算法&#xff0c;就不得不提 Paxos 算法&#xff0c;在過去幾十年里&#xff0c;它基本上是分布式共識的代名詞&#xff0c;因為當前最常用的一批共識算法都是基于它改進的。比…

9.13 9.15 JavaWeb(事務管理、AOP P172-P182)

事務管理事務概念事務是一組操作的集合&#xff0c;是一個不可分割的工作單位&#xff0c;這些操作要么同時成功&#xff0c;要么同時失敗操作開啟事務&#xff08;一組操作開始前&#xff0c;開啟事務&#xff09;&#xff1a;start transaction / begin提交事務&#xff08;這…

檢索融合方法- Distribution-Based Score Fusion (DBSF)

在信息檢索&#xff08;IR&#xff09;、推薦系統和多模態檢索中&#xff0c;我們常常需要融合來自多個檢索器或模型的結果。不同檢索器可能對同一文檔打出的分數差異很大&#xff0c;如果直接簡單加權&#xff0c;很容易出現某個檢索器“主導融合結果”的情況。 Distribution…

Oracle體系結構-歸檔日志文件(Archive Log Files)

核心概念&#xff1a;什么是歸檔日志文件&#xff1f; 定義&#xff1a; 歸檔日志文件&#xff08;Archive Log Files&#xff09;是在線重做日志文件&#xff08;Online Redo Log Files&#xff09;在被覆蓋之前的一個完整副本。它們由 Oracle 的后臺進程 ARCn&#xff08;歸檔…

GoogLeNet實戰:用PyTorch實現經典Inception模塊

配套筆記&講解視頻&#xff0c;點擊文末名片獲取研究背景&#xff08;Background&#xff09; 1.1 領域現狀&#xff08;大環境與挑戰&#xff09; 想象一下&#xff0c;你和朋友們在看一大堆照片——貓、狗、汽車、蛋糕&#xff0c;大家要把每張照片貼上標簽。幾年前&…

【開題答辯全過程】以 “舊書驛站”微信小程序的設計與開發為例,包含答辯的問題和答案

個人簡介一名14年經驗的資深畢設內行人&#xff0c;語言擅長Java、php、微信小程序、Python、Golang、安卓Android等開發項目包括大數據、深度學習、網站、小程序、安卓、算法。平常會做一些項目定制化開發、代碼講解、答辯教學、文檔編寫、也懂一些降重方面的技巧。感謝大家的…

【辦公類-112-01】20250912家園每周溝通指導(Deepseek擴寫完善+Python模擬點擊鼠標自動發送給家長微信)

背景需求 孩子剛上小班,家長比較關心孩子情況(情緒、社交、吃飯等) 所以我每周五晚上和家長溝通一下孩子的情況。 操作流程 第一周(9月5日)是“適應周”,我添加了所有孩子的一位家長的微信號 23份全部是手打,足足寫了4個小時。第一周案例多,所以寫了很多,措辭醞釀后…

Spark專題-第一部分:Spark 核心概述(1)-Spark 是什么?

眾所周知&#xff0c;教學文檔總該以理論部分作為開篇&#xff0c;于是我們這篇Spark專題同樣會以一堆理論和專有名詞開始&#xff0c;筆者會盡可能的讓專業詞匯通俗易懂 第一部分&#xff1a;Spark 核心概述 Spark 是什么&#xff1f; 1. 大數據時代的"超級賽車"…

從零到一上手 Protocol Buffers用 C# 打造可演進的通訊錄

一、為什么是 Protobuf&#xff08;而不是 XML/自定義字符串/.NET 二進制序列化&#xff09; 在需要把結構化對象持久化或跨進程/跨語言傳輸時&#xff0c;常見方案各有痛點&#xff1a; BinaryFormatter 等 .NET 二進制序列化&#xff1a;對類型簽名與版本極其脆弱、體積偏大&…

計算機網絡(三)網絡層

三、網絡層網絡層是五層模型中的第三層&#xff0c;位于數據鏈路層和傳輸層之間。它的核心任務是實現數據包在不同網絡之間&#xff08;跨網絡&#xff09;的邏輯傳輸。網絡層的數據傳輸單位是數據報&#xff08;Datagram&#xff09;或數據包&#xff08;Packet&#xff09;。…

互聯網大廠Java面試實錄:從基礎到微服務全棧技術答疑

互聯網大廠Java面試實錄&#xff1a;從基礎到微服務全棧技術答疑 本文以電商場景為背景&#xff0c;展現一場互聯網大廠Java開發職位的面試過程。嚴肅的面試官與搞笑的水貨程序員謝飛機展開三輪技術問答&#xff0c;涵蓋Java SE、Spring Boot、數據庫、微服務、安全以及CI/CD等…