Spring是如何實現無代理對象的循環依賴

無代理對象的循環依賴

    • 什么是循環依賴
    • 解決方案
      • 實現方式
      • 測試驗證
    • 引入代理對象的影響
      • 創建代理對象
      • 問題分析

源碼見:mini-spring

在這里插入圖片描述

什么是循環依賴

循環依賴是指在對象創建過程中,兩個或多個對象相互依賴,導致創建過程陷入死循環。以下通過一個簡單的例子來說明:

public class A {  @Autowired  private B b;  public void func() {}  public B getB() {  return b;  }  public void setB(B b) {  this.b = b;  }  
}
public class B {  @Autowired  private A a;  public A getA() {  return a;  }  public void setA(A a) {  this.a = a;  }  
}

在上述代碼中,類 A 依賴于 B(通過屬性 b),而類 B 又依賴于 A(通過屬性 a)。如果不加以處理,在創建 A 時會嘗試注入 B,創建 B 時又需要注入 A,從而形成死循環,導致程序無法正常運行。


解決方案

對于沒有代理對象的循環依賴問題,Spring 提供了一種簡單有效的解決方案:提前暴露 Bean。核心思想是在 Bean 實例化完成后(但尚未完成屬性注入),將其加入緩存,從而避免在屬性注入階段因循環依賴而導致的死循環。

實現方式

在 Spring 的 DefaultSingletonBeanRegistry 類中,引入二級緩存 earlySingletonObjects,用于存儲提前暴露的 Bean 實例。雖然從實現角度看,將其放入一級緩存也可以解決問題,但 Spring 使用二級緩存是為了與已完全初始化的 Bean(存儲在一級緩存中)進行區分。

// 二級緩存,保存實例化后的 Bean  
protected Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();

接下來,修改單例 Bean 的獲取邏輯。在 getSingletonBean 方法中,首先從一級緩存 singletonObjects 中查找 Bean,若未找到,則嘗試從二級緩存 earlySingletonObjects 中獲取:

@Override  
public Object getSingletonBean(String beanName) {  Object singletonObject = singletonObjects.get(beanName);  if (singletonObject == null) {  singletonObject = earlySingletonObjects.get(beanName);  }  return singletonObject;  
}

通過上述修改,一個基本的循環依賴解決方案即告完成。

測試驗證

以下是測試代碼,用于驗證循環依賴是否被成功解決:

@Test  
public void testCircularReference() {  ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:circular-reference-without-proxy-bean.xml");  A a = applicationContext.getBean("a", A.class);  B b = applicationContext.getBean("b", B.class);  Assert.assertEquals(a.getB(), b);  
}

在創建 A 對象時,Spring 會在實例化完成后將其加入二級緩存 earlySingletonObjects。隨后在注入屬性時,需要創建 B 對象,而 B 依賴于 A。此時,Spring 直接從二級緩存中獲取 A 實例,完成 B 的創建,并將 B 注入到 A 中,從而成功打破循環依賴。


引入代理對象的影響

如果 Bean 被代理(例如通過 AOP 實現),上述解決方案可能會失效。下面通過一個示例分析問題所在。

創建代理對象

假設對 A 對象應用代理,添加一個前置通知(Before Advice):

@Component  
public class ABefpreAdvice implements MethodBeforeAdvice {  @Override  public void before(Method method, Object[] args, Object target) throws Throwable {  System.out.println("before");  }  
}

Spring 的 XML 配置如下,其中 A 被配置為通過 AOP 代理:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:context="http://www.springframework.org/schema/context"  xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans.xsd  http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-4.0.xsd">  <bean id="b" class="org.qlspringframework.test.bean.B">  <property name="a" ref="a"/>  </bean>  <!-- A 被代理 -->  <bean id="a" class="org.qlspringframework.test.bean.A">  <property name="b" ref="b"/>  </bean>  <bean class="org.qlspringframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>  <bean id="pointcutAdvisor" class="org.qlspringframework.aop.aspectj.AspectJExpressionPointcutAdvisor">  <property name="expression" value="execution(* org.qlspringframework.test.bean.A.func(..))"/>  <property name="advice" ref="methodInterceptor"/>  </bean>  <bean id="methodInterceptor" class="org.qlspringframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">  <property name="advice" ref="beforeAdvice"/>  </bean>  <bean id="beforeAdvice" class="org.qlspringframework.test.common.ABefpreAdvice"/>  
</beans>

問題分析

在引入代理對象后,測試結果會發生變化。B 中注入的 A 實例是原始對象(實例化后但未完成初始化的對象),而從 Spring 容器中最終獲取的 A 是代理對象,二者不再是同一個對象。這是因為 Spring 的二級緩存機制保存的是未代理的 A 實例,而代理對象是在后續階段生成的。

因此,Assert.assertEquals(a.getB(), b) 可能失敗,因為 a 是代理對象,而 b 中持有的 a 是原始對象。

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

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

相關文章

Android 之 kotlin 語言學習筆記三(Kotlin-Java 互操作)

參考官方文檔&#xff1a;https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java&#xff08;供 Kotlin 使用&#xff09; 1、不得使用硬關鍵字 不要使用 Kotlin 的任何硬關鍵字作為方法的名稱 或字段。允許使用 Kotlin 的軟關鍵字、修飾符關鍵字和特殊標識…

從 GreenPlum 到鏡舟數據庫:杭銀消費金融湖倉一體轉型實踐

作者&#xff1a;吳岐詩&#xff0c;杭銀消費金融大數據應用開發工程師 本文整理自杭銀消費金融大數據應用開發工程師在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合數據湖與數倉的創新之路 在數字金融時代&#xff0c;數據已成為金融機構的核心競爭力。杭銀消費金…

Bean 作用域有哪些?如何答出技術深度?

導語&#xff1a; Spring 面試繞不開 Bean 的作用域問題&#xff0c;這是面試官考察候選人對 Spring 框架理解深度的常見方式。本文將圍繞“Spring 中的 Bean 作用域”展開&#xff0c;結合典型面試題及實戰場景&#xff0c;幫你厘清重點&#xff0c;打破模板式回答&#xff0c…

基于 Spring Boot 策略模式的短信服務提供商動態切換實現

一、整體設計思路 為了實現在短信服務提供商變更時,不修改現有代碼就能無縫切換到新服務實現,可采用策略模式結合依賴注入以及配置中心化管理的方式來設計軟件系統。 二、 具體實現步驟 1. 定義統一接口(以短信服務為例,接口命名為 SmsService) 創建一個抽象的接口,用…

解決SQL Server SQL語句性能問題(9)——SQL語句改寫(1)

9.4. SQL語句改寫 目前主流關系庫的高版本中,特別是作為主流商業關系庫的SQL Server來講,大部分場景中,同一語義和結果集的SQL語句,其不同寫法并不會影響CBO為SQL語句生成和選擇最合適、最高效的查詢計劃。但少數情況下,不同寫法的同一語義和結果集的SQL語句,CBO也許會為…

設計模式復習小結

1.容易忘得設計原則 接口隔離&#xff1a;指接口中的功能太雜則可以拆分一下。防止實現類實現了接口后自動依賴了一些不需要的功能。不同功能拆分成不同的接口。 里氏代換&#xff1a;強調父類能出現的地方&#xff0c;子類一定能正常跑。 迪米特法則&#xff1a;又稱最少知…

昇騰CANN集合通信技術解讀——細粒度分級流水算法

隨著AI技術的演進&#xff0c;模型的計算復雜度和參數量呈現幾何級數增長&#xff0c;這使得傳統單機單卡部署在算力供給與顯存容量方面顯得力不從心&#xff0c;從而直接推動了分布式訓練/推理技術的快速發展。今年年初爆火的DeepSeek在訓練及推理Prefill階段采用了分級流水Al…

水泥廠自動化升級利器:Devicenet轉Modbus rtu協議轉換網關

在水泥廠的生產流程中&#xff0c;工業自動化網關起著至關重要的作用&#xff0c;尤其是JH-DVN-RTU疆鴻智能Devicenet轉Modbus rtu協議轉換網關&#xff0c;為水泥廠實現高效生產與精準控制提供了有力支持。 水泥廠設備眾多&#xff0c;其中不少設備采用Devicenet協議。Devicen…

使用Matplotlib創建炫酷的3D散點圖:數據可視化的新維度

文章目錄 基礎實現代碼代碼解析進階技巧1. 自定義點的大小和顏色2. 添加圖例和樣式美化3. 真實數據應用示例實用技巧與注意事項完整示例(帶樣式)應用場景在數據科學和可視化領域,三維圖形能為我們提供更豐富的數據洞察。本文將手把手教你如何使用Python的Matplotlib庫創建引…

Copilot for Xcode (iOS的 AI輔助編程)

Copilot for Xcode 簡介Copilot下載與安裝 體驗環境要求下載最新的安裝包安裝登錄系統權限設置 AI輔助編程生成注釋代碼補全簡單需求代碼生成輔助編程行間代碼生成注釋聯想 代碼生成 總結 簡介 嘗試使用了Copilot&#xff0c;它能根據上下文補全代碼&#xff0c;快速生成常用…

React 進階特性

1. ref ref 是 React 提供的一種機制,用于訪問和操作 DOM 元素或 React 組件的實例。它可以用于獲取某個 DOM 元素的引用,從而執行一些需要直接操作 DOM 的任務,例如手動設置焦點、選擇文本或觸發動畫。 1.1. 使用 ref 的步驟 1. 創建一個 ref:使用 React.createRef 或 …

基于PHP的連鎖酒店管理系統

有需要請加文章底部Q哦 可遠程調試 基于PHP的連鎖酒店管理系統 一 介紹 連鎖酒店管理系統基于原生PHP開發&#xff0c;數據庫mysql&#xff0c;前端bootstrap。系統角色分為用戶和管理員。 技術棧 phpmysqlbootstrapphpstudyvscode 二 功能 用戶 1 注冊/登錄/注銷 2 個人中…

【大廠機試題解法筆記】報文響應時間

題目 IGMP 協議中&#xff0c;有一個字段稱作最大響應時間 (Max Response Time) &#xff0c;HOST收到查詢報文&#xff0c;解折出 MaxResponseTime 字段后&#xff0c;需要在 (0&#xff0c;MaxResponseTime] 時間 (s) 內選取隨機時間回應一個響應報文&#xff0c;如果在隨機…

邏輯回歸暴力訓練預測金融欺詐

簡述 「使用邏輯回歸暴力預測金融欺詐&#xff0c;并不斷增加特征維度持續測試」的做法&#xff0c;體現了一種逐步建模與迭代驗證的實驗思路&#xff0c;在金融欺詐檢測中非常有價值&#xff0c;本文作為一篇回顧性記錄了早年間公司給某行做反欺詐預測用到的技術和思路。百度…

Python爬蟲實戰:研究demiurge框架相關技術

1. 引言 在當今數字化時代,互聯網上蘊含著海量的有價值信息。爬蟲技術作為獲取這些信息的重要手段,被廣泛應用于學術研究、商業分析、輿情監測等多個領域。然而,構建一個高效、穩定且可維護的爬蟲系統面臨諸多挑戰,如網頁結構復雜多變、反爬機制日益嚴格、數據處理流程繁瑣…

Jenkins | Jenkins構建成功服務進程關閉問題

Jenkins構建成功服務進程關閉問題 1. 原因2. 解決 1. 原因 Jenkins 默認會在構建結束時終止所有由構建任務啟動的子進程&#xff0c;即使使用了nohup或后臺運行符號&。 2. 解決 在啟動腳本中加上 BULID_IDdontkillme #--------------解決jenkins 自動關閉進程問題-----…

深度學習習題2

1.如果增加神經網絡的寬度&#xff0c;精確度會增加到一個特定閾值后&#xff0c;便開始降低。造成這一現象的可能原因是什么&#xff1f; A、即使增加卷積核的數量&#xff0c;只有少部分的核會被用作預測 B、當卷積核數量增加時&#xff0c;神經網絡的預測能力會降低 C、當卷…

猜字符位置游戲-position gasses

import java.util.*;public class Main {/*字符猜位置游戲;每次提交只能被告知答對幾個位置;根據提示答對的位置數推測出每個字符對應的正確位置;*/public static void main(String[] args) {char startChar A;int gameLength 8;List<String> ballList new ArrayList&…

解析兩階段提交與三階段提交的核心差異及MySQL實現方案

引言 在分布式系統的事務處理中&#xff0c;如何保障跨節點數據操作的一致性始終是核心挑戰。經典的兩階段提交協議&#xff08;2PC&#xff09;通過準備階段與提交階段的協調機制&#xff0c;以同步決策模式確保事務原子性。其改進版本三階段提交協議&#xff08;3PC&#xf…

Towards Open World Object Detection概述(論文)

論文&#xff1a;https://arxiv.org/abs/2103.02603 代碼&#xff1a;https://github.com/JosephKJ/OWOD Towards Open World Object Detection 邁向開放世界目標檢測 Abstract 摘要 Humans have a natural instinct to identify unknown object instances in their environ…