Spring 循環依賴:從原理到解決方案的全面解析

Spring 循環依賴:從原理到解決方案的全面解析

一、循環依賴的定義與分類

1. 什么是循環依賴?

在 Spring 框架中,循環依賴指的是多個 Bean 之間形成了依賴閉環。例如:

  • Bean A 依賴 Bean B
  • Bean B 依賴 Bean C
  • Bean C 又依賴 Bean A
    此時,這三個 Bean 之間就形成了循環依賴關系。當容器嘗試初始化這些 Bean 時,會陷入無法完成初始化的死循環。

2. 循環依賴的三種類型

根據依賴注入方式的不同,循環依賴可分為三類:

類型描述
構造器循環依賴多個 Bean 通過構造函數互相依賴,如A構造器注入BB構造器注入A
setter 循環依賴多個 Bean 通過 setter 方法互相依賴,如A.setB(b)B.setA(a)
字段注入循環依賴多個 Bean 通過字段直接注入互相依賴,如@Autowired private B b;@Autowired private A a;

二、Spring 如何處理循環依賴?

1. Spring 處理循環依賴的核心機制

Spring 通過三級緩存機制解決 setter 和字段注入的循環依賴,而構造器循環依賴則無法自動解決。

三級緩存的定義(DefaultSingletonBeanRegistry類):
  1. 一級緩存(singletonObjects):存儲完全初始化的 Bean 實例。
  2. 二級緩存(earlySingletonObjects):存儲已創建但未完全初始化的 Bean 實例(早期暴露的對象)。
  3. 三級緩存(singletonFactories):存儲 Bean 的工廠對象,用于生成代理對象等后置處理。

2. 解決 setter 循環依賴的流程示例

A依賴B,B依賴A的 setter 循環依賴為例:

  1. 初始化 A:創建 A 的實例,放入二級緩存,并標記為 “未完全初始化”。
  2. 注入 B 到 A:發現 A 依賴 B,開始初始化 B。
  3. 初始化 B:創建 B 的實例,放入二級緩存,然后嘗試注入 A 到 B。
  4. 注入 A 到 B:此時 A 已在二級緩存中,B 獲取 A 的早期實例并完成注入,B 初始化完成后放入一級緩存。
  5. 完成 A 的初始化:A 獲取到已初始化的 B,完成注入后放入一級緩存。

3. 三級緩存的核心作用

  • 三級緩存的存在是為了處理 AOP 代理:當 Bean 需要代理時,三級緩存存儲的工廠對象會在早期暴露階段生成代理實例,避免循環依賴中出現 “原始對象” 和 “代理對象” 的不一致問題。

三、構造器循環依賴為何無法解決?

1. 構造器循環依賴的初始化流程

假設A構造器注入BB構造器注入A

  1. 初始化 A 時,需要先創建 B 的實例。
  2. 初始化 B 時,又需要先創建 A 的實例。
  3. 由于構造器依賴必須在對象創建時完成,兩者互相等待,導致初始化阻塞。

2. 示例代碼與異常

@Component
public class A {private final B b;// 構造器注入B,導致循環依賴public A(B b) {this.b = b;}
}@Component
public class B {private final A a;public B(A a) {this.a = a;}
}

啟動 Spring 容器時會拋出org.springframework.beans.factory.UnsatisfiedDependencyException,提示無法解析循環依賴。

四、循環依賴的解決方案

1. 針對構造器循環依賴:

方案一:使用 setter 注入替代構造器注入
@Component
public class A {private B b;// 使用setter注入,允許Spring通過三級緩存解決循環依賴public void setB(B b) {this.b = b;}
}
方案二:使用 @Lazy 延遲初始化

通過@Lazy讓 Spring 注入代理對象,延遲依賴解析:

@Component
public class A {private final B b;// 注入B的代理對象,避免初始化時立即創建Bpublic A(@Lazy B b) {this.b = b;}
}
方案三:拆分 Bean,打破依賴鏈

將復雜 Bean 拆分為多個小 Bean,避免直接依賴。

2. 針對 setter / 字段循環依賴:

通常無需特殊處理,Spring 三級緩存可自動解決。若遇到問題,可能是以下原因:

  • Bean 使用了@PostConstruct等初始化方法,且方法中存在循環邏輯。
  • 自定義 BeanPostProcessor 干擾了三級緩存的正常工作。

3. 通用最佳實踐:

  1. 優先使用構造器注入:明確依賴關系,但需避免構造器循環依賴。
  2. 謹慎使用 @Autowired 字段注入:可能隱藏依賴關系,推薦搭配 setter 注入。
  3. 使用 @DependsOn:強制指定 Bean 初始化順序,打破隱性循環依賴。
  4. 模塊化設計:通過拆分服務或引入中間層,避免跨模塊的直接依賴。

五、Spring Boot 中循環依賴的排查與工具

1. 日志排查

啟動時添加 JVM 參數-Dspring.main.allow-circular-references=true(Spring Boot 2.6+),允許循環依賴并打印警告日志。

2. IDE 工具輔助

  • IntelliJ IDEA:通過Analyze Dependencies功能檢測循環依賴。
  • Spring Tool Suite:使用依賴分析視圖定位問題 Bean。

3. 編程式排查

通過ConfigurableApplicationContext.getBeanFactory()獲取DefaultListableBeanFactory,調用isPrototypeCurrentlyInCreation()等方法診斷循環依賴。

六、深度解析:三級緩存的源碼視角

1. 關鍵源碼路徑(AbstractBeanFactory.doGetBean):

// 從一級緩存獲取Bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && !isSingletonCurrentlyInCreation(beanName)) {return getObjectForBeanInstance(sharedInstance, name, beanName, null);
}// 標記Bean為“正在創建”
beforeSingletonCreation(beanName);
try {// 從二級緩存獲取早期實例sharedInstance = getSingleton(beanName, false);if (sharedInstance != null) {// 處理早期實例return getObjectForBeanInstance(sharedInstance, name, beanName, null);}// 創建Bean實例(未初始化)BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);Object bean = instanceWrapper.getWrappedInstance();// 將早期實例放入三級緩存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));// 填充依賴(可能觸發循環依賴)populateBean(beanName, mbd, instanceWrapper);// 初始化Bean(調用后置處理器等)exposedObject = initializeBean(beanName, exposedObject, mbd);// 將完全初始化的Bean放入一級緩存addSingleton(beanName, exposedObject);
} finally {afterSingletonCreation(beanName);
}

2. 核心方法解析:

  • getSingleton(beanName, true):嘗試從一級緩存獲取 Bean,若不存在則創建。
  • addSingletonFactory:將 Bean 的工廠對象存入三級緩存,用于生成早期實例。
  • getEarlyBeanReference:處理 AOP 代理等后置操作,返回早期實例。

七、總結:循環依賴的本質與設計哲學

Spring 通過三級緩存解決循環依賴的核心,是利用 “早期暴露” 機制打破初始化死鎖:將未完全初始化的 Bean 提前暴露到二級緩存,允許其他 Bean 先獲取其引用,后續再完成初始化。

但構造器循環依賴無法解決,這體現了 Spring 的設計原則:構造器依賴應代表 “強依賴”,而強依賴不應形成循環。在實際開發中,合理的依賴設計(如模塊化、單向依賴)比依賴 Spring 的循環依賴處理機制更重要。

理解循環依賴的原理與解決方案,不僅能幫助開發者快速定位問題,還能加深對 Spring Bean 生命周期和依賴注入機制的理解,從而寫出更健壯的代碼。

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

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

相關文章

n 階矩陣 A 可逆的充分必要條件是 ∣ A ∣ ≠ 0

n 階矩陣 A 可逆的充分必要條件是 ∣ A ∣ ≠ 0 |A| \neq 0 ∣A∣0 的幾何意義 1. 行列式的幾何意義回顧 行列式 ∣ A ∣ |A| ∣A∣(或 det ? ( A ) \det(A) det(A))表示矩陣 A A A 所對應的線性變換對空間的體積縮放因子: ∣ A ∣ &…

Rockey Linux 安裝ffmpeg

1.環境準備 Rockey linux 9.2 ffmpeg 靜態資源包 這個是我自己的: https://download.csdn.net/download/liudongyang123/90920340https://download.csdn.net/download/liudongyang123/90920340 這個是官網的 Releases BtbN/FFmpeg-Builds GitHub 以上兩個資…

wordcount在集群上的測試

1.將louts.txt文件從cg計算機復制到master節點上面,存放在/usr/local/hadoop 需要輸入密碼:83953588abc scp /root/IdeaProjects/mapReduceTest/lotus.txt root172.18.0.2:/usr/local/hadoop /WordCountTest/input 2.將lotus.txt文件從master這臺機器…

AI+制造:中小企業的低成本智能化轉型

文章內容過長,可以考慮直接跳轉到文章末尾查看概要圖 在制造業競爭日益激烈的今天,中小企業正面臨著前所未有的挑戰:人力成本持續攀升、能源消耗居高不下、質量控制難度增加。與此同時,數字化轉型已成為行業共識,但高…

Linux C/C++編程 —— 線程技術總結

一、線程基本概念 線程是進程內的一個執行單元,多個線程共享進程的資源(如內存、文件描述符等),但每個線程擁有自己的寄存器、棧等。與進程相比,線程的創建、切換開銷較小,能更高效地利用 CPU 資源。 二、…

Femap許可證與網絡安全策略

隨著科技的快速發展,網絡安全問題已成為各行各業關注的焦點。在電磁仿真領域,Femap作為一款領先的軟件,其許可證的安全性和網絡策略的重要性不言而喻。本文將探討Femap許可證與網絡安全策略的關系,確保您的電磁仿真工作能夠在一個…

深度解析:SQLynx 如何筑牢數據庫安全防線?

在數據驅動業務發展的時代,數據庫作為企業核心資產的 “保險箱”,其安全性至關重要。一旦數據庫遭遇攻擊、數據泄露或被誤操作,將給企業帶來不可估量的損失。而 SQLynx 作為一款專注于數據庫安全管理的工具,憑借其多項創新技術與功…

更新時間相差8個小時

下面的java代碼在updateFill方法里面生成的modifiedTime時間是當前時間是正確的,為什么到service層testCommonFieldAutoUpdate方法里面去更新的時候modifiedTime就差8個小時呢?代碼如下所示: Slf4j Component public class MpMetaObjectHand…

Windows逆向工程提升之IMAGE_TLS_DIRECTORY

公開視頻 -> 鏈接點擊跳轉公開課程博客首頁 -> ???鏈接點擊跳轉博客主頁 目錄 TLS的作用 TLS的實現 靜態 TLS?? 動態 TLS?? 內部實現 回調機制 TLS Directory 的結構 TLS的作用 TLS (Thread Local Storage) 是一種用于為多線程應用程序提供線程獨立存儲空…

云效流水線Flow使用記錄

概述 最近在頻繁使用阿里云云效的幾款產品,如流水線。之前寫過一篇,參考云效流水線緩存問題。 這篇文章來記錄更多問題。 環境變量 不管是云效流水線Flow還是應用交付AppStack(基于流水線,后文不再贅述)&#xff0…

Android中獲取控件尺寸進階方案

在Android開發中,很多場景都需要獲取控件(View)的寬高信息,比如動態布局調整、動畫效果實現等。然而,直接在Activity的onCreate()中調用控件的getWidth()或getHeight()`方法,得到結果卻是0,因為控件還沒完成布局測量。 本文總結了幾種獲取控件大小的實用方法,并對各方…

android 輸入系統

一、輸入系統的核心角色與分層架構 Android 輸入系統的本質是橋梁:一端連接硬件驅動產生的原始事件,另一端將事件精準派發給應用窗口。整個過程涉及三層架構和多個關鍵組件,可類比為 “快遞分揀系統”: 1. 硬件與內核層&#xf…

ubuntu中,c和c+程序,預編譯、編譯、鏈接和運行命令

目錄 安裝編譯器一.c編譯運行(粗暴簡單)1.編寫 C 程序:2. 預處理:3.編譯:4. 匯編:5. 鏈接:6.運行 二.c編譯運行(粗暴簡單)1.編寫 C 程序:2.預處理&#xff1a…

【FastAPI】--2.進階教程(一)

【FastAPI】--基礎教程-CSDN博客 app.get和post的參數: 參數類型說明pathstr路由路徑(如 "/marks"),必填。response_modelType[T]定義響應數據的模型(如 percent),會自動校驗和序列…

KT6368A通過藍牙芯片獲取手機時間詳細說明,對應串口指令舉例

一、功能簡介 KT6368A雙模藍牙芯片支持連接手機,獲取手機的日期、時間信息,可以同步RTC時鐘 1、無需安裝任何app,直接使用系統藍牙即可實現 2、同時它不影響音頻藍牙,還支持一些簡單的AT指令進行操作 3、實現的方式&#xff1…

【平面波導外腔激光器專題系列】用于光纖傳感的低噪聲PLC外腔窄線寬激光器

----翻譯自Mazin Alalusi等人的文章 摘要 高性價比的 1550 nm DWDM平面外腔 &#xff08;PLANEX&#xff09; 激光器是干涉測量、布里淵、LIDAR 和其他光傳感應用的最佳選擇。其線寬<3kHz、低相位/頻率噪聲和極低的RIN。 簡介 高性能光纖分布式傳感技術是在過去幾年中開發…

企業微信內部網頁開發流程筆記

背景 基于ai實現企微側邊欄和工作臺快速問答小助&#xff0c;需要h5開發&#xff0c;因為流程不清楚摸索半天&#xff0c;所以記錄一下 一、網頁授權登錄 1. 配置步驟 1.1 設置可信域名 登錄企業微信管理后臺 進入"應用管理" > 選擇開發的具體應用 > “網…

WORD 轉 PDF 工具:排版 / 圖片 / 表格批量轉換提升辦公效率

各位辦公小能手們&#xff0c;今天來聊聊文檔工具里的WORD轉PDF工具&#xff01;這玩意兒到底是干啥的呢&#xff1f;咱來好好說道說道。 先說核心功能。第一個就是格式轉換&#xff0c;能把Word文檔轉換成PDF&#xff0c;不管是格式、排版&#xff0c;還是圖片、表格啥的&…

從逆流監測到智慧用電:ADL200N-CT系列單相導軌表賦能家庭綠色能源

在新能源浪潮席卷全球的當下&#xff0c;陽臺光伏以及家庭儲能&#xff08;戶儲&#xff09;系統逐漸成為眾多追求綠色生活、渴望實現能源自主家庭的新選擇。它不僅能有效利用太陽能等清潔能源&#xff0c;還能在用電高峰時段為家庭提供穩定電力支持&#xff0c;降低用電成本。…

std::thread的說明與示例

源于通義千問 在 C 中&#xff0c;std::thread 支持傳遞多種類型的函數作為線程入口點。你可以傳遞普通函數、類的成員函數、Lambda 表達式、函數對象&#xff08;仿函數&#xff09;等。以下是詳細的說明和示例。 1. 傳遞普通函數 普通函數是最簡單的用法。 示例 #include…