Spring循環依賴源碼調試詳解,用兩級緩存代替三級緩存

Spring循環依賴源碼詳解,改用兩級緩存并實驗

背景

最近一直在研究Spring的循環依賴,發現好像兩級緩存也能解決循環依賴。
關于為何使用三級緩存,大致有兩個原因

  1. 對于AOP的類型,保證Bean生命周期的順序
    對于有AOP代理增強的類型,如果沒有循環依賴,那么AOP的增強邏輯的執行點在:
無循環依賴:
Container->>Bean: 1. 實例化(constructor)Container->>Bean: 2. 屬性注入(populate)Container->>Processor: 3. 調用postProcessAfterInitialization()Processor->>Processor: 4. 創建代理(wrapIfNecessary)Processor-->>Container: 5. 返回代理對象

在第4步,也就是初始化后置處理器postProcessAfterInitialization
實際代理包裝在BeanPostProcessor子類AbstractAutoProxyCreator類中:

public Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean); // 標記為提前代理return wrapIfNecessary(bean, beanName, cacheKey); // 創建代理
}
有循環依賴
    Container->>Bean: 1. 實例化(半成品)Container->>EarlyCache: 2. 存入singletonFactoriesContainer->>Bean: 3. 屬性注入(觸發循環依賴)Container->>EarlyCache: 4. 獲取早期引用 → getEarlyBeanReference()EarlyCache->>Processor: 5. 調用getEarlyBeanReference()Processor->>Processor: 6. 創建代理(提前)Processor-->>Container: 7. 返回代理對象Container->>Bean: 8. 繼續屬性注入和初始化Container->>Processor: 9. postProcessAfterInitialization() Processor-->>Container: 10. 返回同一個代理(已存在則不重復創建)

這里是在循環依賴注入的過程中發生的,提前了

其實在哪里進行代理并無實際影響,因為不會影響類實例的成員

2、第二個原因
是在實例化后依賴注入之前,會把這個ObjectFactory的對象放到三級緩存,延遲創建代理實例,后續有循環依賴,回到三級緩存拿到這個,并調用ObjectFactory.getObject方法進行真正的創建,多次調用會產生多個實例,這里可以及時創建實例,不必等到延遲加載,就解決了

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

改用兩級緩存

(針對單例循環setter場景,修改spring源碼,三級緩存改為兩級緩存)

/*** 修改:20250819 11:57 直接加入二級緩存  不用三級緩存 看一下能不能解決循環依賴* @param beanName* @param singletonFactory*/protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {//this.singletonFactories.put(beanName, singletonFactory); // 加入工廠 暴露給外部this.earlySingletonObjects.put(beanName,singletonFactory.getObject());// 確保二級緩存不會存在相同的beanthis.registeredSingletons.add(beanName);}}}

這里直接把三級緩存注釋,在實例化完成后直接生成代理對象

創建測試類

@Aspect  // 切面類
@Component
public class LogAspect {// 切入點表達式:匹配所有 MyServiceImpl 下的方法@Pointcut("execution(* com.jdkProxy.MyServiceImpl.*(..))")public void userServiceMethods() {// 方法體必須為空,不能寫任何邏輯!}// 前置通知:方法執行前@Before("userServiceMethods()")public void beforeMethod(JoinPoint joinPoint) {System.out.println("📌 Before method: " + joinPoint.getSignature().getName());}// 后置通知:方法正常返回后@AfterReturning(pointcut = "userServiceMethods()", returning = "result")public void afterReturning(JoinPoint joinPoint, Object result) {System.out.println("? Method returned: " + joinPoint.getSignature().getName());}// 異常通知:方法拋出異常后@AfterThrowing(pointcut = "userServiceMethods()", throwing = "ex")public void afterThrowing(JoinPoint joinPoint, Exception ex) {System.out.println("💥 Exception in method: " + joinPoint.getSignature().getName() + ", ex: " + ex);}// 環繞通知:可以控制整個方法執行@Around("userServiceMethods()")public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("🔄 Around before: " + pjp.getSignature().getName());Object result = pjp.proceed(); // 執行目標方法System.out.println("🔄 Around after: " + pjp.getSignature().getName());return result;}
}
@Component
public class MyServiceImpl implements MyService{@AutowiredMyServiceImpl2 myServiceImpl2;public MyServiceImpl2 getMyServiceImpl2() {return myServiceImpl2;}public void setMyServiceImpl2(MyServiceImpl2 myServiceImpl2) {this.myServiceImpl2 = myServiceImpl2;}public void eat(){System.out.println("吃飯服務");}@Overridepublic void mainMethod() {eat();}
}
@Component
public class MyServiceImpl2 {@AutowiredMyServiceImpl myService;public MyServiceImpl getMyService() {return myService;}public void setMyService(MyServiceImpl myService) {this.myService = myService;}
}
@Component
public class MyServiceImpl3 {@AutowiredMyServiceImpl myService;public MyServiceImpl getMyService() {return myService;}public void setMyService(MyServiceImpl myService) {this.myService = myService;}
}
MyServiceImpl ->myServiceImpl2
MyServiceImpl2 -> MyServiceImpl 
MyServiceImpl3 ->MyServiceImpl 
啟動容器
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();context.scan("com"); // 掃描包中的注解 進行BeanDefinintion 注冊context.refresh();MyServiceImpl m1 = context.getBean(MyServiceImpl.class);System.out.println("m1->m2:"+m1.getMyServiceImpl2());MyServiceImpl2 m2 = context.getBean(MyServiceImpl2.class);MyServiceImpl3 m3 = context.getBean(MyServiceImpl3.class);System.out.println("m1:"+m1);System.out.println("m2->m1:"+m2.getMyService());m2.getMyService().eat();System.out.println("m3->m1:"+m3.getMyService());m3.getMyService().eat();

輸出結果:

m1:com.jdkProxy.MyServiceImpl@29c2c826
m2:com.jdkProxy.MyServiceImpl2@253b380a
m3:com.jdkProxy.MyServiceImpl3@6818d900
------------------------------------------
🔄 Around before: getMyServiceImpl2
📌 Before method: getMyServiceImpl2
? Method returned: getMyServiceImpl2
🔄 Around after: getMyServiceImpl2
m1->m2:com.jdkProxy.MyServiceImpl2@253b380a
m2->m1:com.jdkProxy.MyServiceImpl@29c2c826
m3->m1:com.jdkProxy.MyServiceImpl@29c2c826
------------------------------------------
🔄 Around before: eat
📌 Before method: eat
吃飯服務
? Method returned: eat
🔄 Around after: eat
------------------------------------------
🔄 Around before: eat
📌 Before method: eat
吃飯服務
? Method returned: eat
🔄 Around after: eat
🔄 Around before: eat
📌 Before method: eat
吃飯服務
? Method returned: eat
🔄 Around after: eat
🔄 Around before: mainMethod
📌 Before method: mainMethod
吃飯服務
? Method returned: mainMethod
🔄 Around after: mainMethod
可以看到m1、m2、m3都是單例的,代理類也正常,所以兩級緩存可以解決循環依賴,在有代理的情況下

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

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

相關文章

亞馬遜BALL PIT球池外觀專利侵權指控?不侵權意見書助力4條鏈接申訴成功!

兒童球池作為玩具品類中常見的一款產品&#xff0c;能夠給兒童提供游樂的安全空間&#xff0c;深受亞馬遜平臺用戶的喜愛。然而在近期&#xff0c;賽貝收到了部分亞馬遜賣家的咨詢&#xff0c;原因是他們在售的兒童球池產品鏈接被美國外觀專利USD1009203S&#xff08;下稱203專…

開源,LangExtract-Python庫用LLM從非結構化文本提取結構化信息

摘要&#xff1a; LangExtract是一個Python庫&#xff0c;利用大語言模型&#xff08;LLM&#xff09;根據用戶定義指令從非結構化文本文檔中提取結構化信息。它具備精確源定位、可靠結構化輸出、長文檔優化、交互式可視化、靈活LLM支持、適應任意領域等特點。可通過幾行代碼快…

如何根據團隊技術能力選擇最適合的PHP框架?

作為一名PHP開發者&#xff0c;面對眾多的PHP框架&#xff0c;你是否曾感到選擇困難&#xff1f;Laravel、Symfony、CodeIgniter、ThinkPHP…每個框架都有其特色和優勢&#xff0c;但沒有最好的框架&#xff0c;只有最適合的框架。而選擇合適框架的關鍵因素之一&#xff0c;就是…

多人同時導出 Excel 導致內存溢出

1、問題根因分析多人同時導出Excel導致內存溢出&#xff08;OOM&#xff09;的核心原因是&#xff1a;在短時間內&#xff0c;大量數據被加載到JVM堆內存中&#xff0c;且創建了大量大對象&#xff08;如Apache POI的Cell、Row、Sheet對象&#xff09;&#xff0c;超過了堆內存…

深入 RAG(檢索增強生成)系統架構:如何構建一個能查資料的大語言模型系統

&#x1f407;明明跟你說過&#xff1a;個人主頁 &#x1f3c5;個人專欄&#xff1a;《深度探秘&#xff1a;AI界的007》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目錄 一、前言 1、LLM 的局限&#xff1a;模型知識“封閉” vs 現實知識…

linux tftpboot燒寫地址分析

1&#xff0c;loadaddr 是一個環境變量&#xff0c;用于指定文件&#xff08;如內核鏡像、設備樹等&#xff09;加載到內存的起始地址。setenv loadaddr 0x82000000setenv loadaddr 0x80008000saveenv //.保存配置將 loadaddr 設置為 0x82000000&#xff0c;表示后續文件將加載…

硬件工程師9月實戰項目分享

目錄 簡介 人員情況 實戰項目簡介 功能需求 需求分析 方案設計 電源樹設計 時鐘樹設計 主芯片外圍設計 接口設計 模擬鏈路設計 PCB設計檢查要點 測試方案設計 硬件測試培訓 測試代碼學習 培訓目標 掌握基本的硬件設計流程 掌握以FPGA為核心的硬件設計業務知識 …

力扣刷題——59.螺旋矩陣II

力扣刷題——59.螺旋矩陣II 題目 給你一個正整數 n &#xff0c;生成一個包含 1 到 n2 所有元素&#xff0c;且元素按順時針順序螺旋排列的 n x n 正方形矩陣 matrix 。示例 1&#xff1a;輸入&#xff1a;n 3 輸出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; 輸…

win11系統還原點恢復系統

背景 系統換位bug11后&#xff0c;真的是各種以前的操作和設置找不到&#xff0c;太煩了&#xff0c;我是沒想到&#xff0c;連系統恢復還原點都這么難找。然后搜了一圈都是恢復系統之類的&#xff0c;真的崩潰。只好自己記錄了。 ?內容找到設置—>系統–>系統信息系統信…

DHCP 原理與配置(一)

應用場景隨著網絡規模的不斷擴大&#xff0c;網絡復雜度不斷提升&#xff0c;網絡中的終端設備例如主機、手機、 平板等&#xff0c;位置經常變化。終端設備訪問網絡時需要配置IP地址、網關地址、DNS服務器 地址等。采用手工方式為終端配置這些參數非常低效且不夠靈活。 IETF于…

SARibbon的編譯構建及詳細用法

目錄 1.1 源碼構建 1.2 搭建項目 1.3 詳細用法 1.4 不同風格 1.5 完整代碼 引言:SARibbon是一個專門為Qt框架設計的開源Ribbon風格界面控件庫,它模仿了微軟Office和WPS的Ribbon UI風格,適用于需要復雜菜單和工具欄的大型桌面程序。本文從源碼編譯構建到詳細使用,做了一…

CSS【詳解】性能優化

精簡 CSS移除未使用的 CSS&#xff08;“死代碼”&#xff09;&#xff0c;可借助工具如 PurgeCSS、UnCSS 自動檢測并刪除未被頁面使用的樣式。避免重復樣式&#xff0c;通過提取公共樣式&#xff08;如 mixin 或公共類&#xff09;減少代碼冗余。利用預處理器&#xff08;Sass…

Flutter 線程模型詳解:主線程、異步與 Isolate

一、主線程&#xff1a;默認的執行環境 所有代碼默認運行在主線程。下面的例子展示了一個會阻塞主線程的錯誤示范&#xff1a; import package:flutter/material.dart;void main() {runApp(const MyApp()); }class MyApp extends StatelessWidget {const MyApp({super.key});ov…

ChartDB:可視化數據庫設計工具私有化部署

ChartDB:可視化數據庫設計工具私有化部署一、什么是ChartDB ChartDB 是一款基于 Web 的開源數據庫可視化工具&#xff0c;專為簡化數據庫設計與管理流程而開發。以下是其核心特性與功能概述: 1、核心功能 智能查詢可視化?&#xff1a;通過單條 SQL 查詢即可生成數據庫架構圖&a…

單片機-FreeRTOS(ing)

目錄 一、基礎介紹 1.1 調度策略 1.1.1 調度方式 1.1.2 調度器 1.2 任務以及優先級 1.2.1 任務與協程 1.2.2 任務狀態 1.2.3 任務優先級 1.2.4 任務優先級分配方案 1.3 任務間通信 - 信號量 1.3.1 信號量 1.3.2 任務間計數信號量的實現 1.3.3 中斷方式計數信號量的…

為什么調用API總返回404,該如何調試

當調用一個應用程序接口&#xff08;API&#xff09;時&#xff0c;持續地收到“404 未找到”的錯誤&#xff0c;其核心原因在于客戶端發起的“請求”&#xff0c;未能成功地&#xff0c;匹配到服務器上任何一個“真實存在”的、可供訪問的“資源路徑”。這本質上&#xff0c;是…

醫療信息化自主可控轉型的實踐探索 —— 以常德二院為例

目錄 頭雁領航 - 激發醫療新質生產力 核心支撐 - 電科金倉奠定數據底座 生態共建 - 攜手護航醫療信創發展 信創產業發展是國家經濟數字化轉型、提升產業鏈發展的關鍵&#xff0c;是科技自立自強的核心基座&#xff0c;其本質是實現中國信息化產業的自主可控。醫療信創作為關…

Gin傳參和接收參數的方式

Gin查詢參數和接收參數的方式 常用 Gin 綁定方法對比方法用途特點c.Bind()自動識別 Content-Type最通用&#xff0c;根據請求頭自動選擇綁定方式c.ShouldBindJSON()只綁定 JSON強制使用 JSON 格式&#xff0c;類型明確c.ShouldBindXML()只綁定 XML強制使用 XML 格式c.ShouldBin…

MariaDB/MySQL 客戶端工具與服務端配置精要指南

文章目錄一、客戶端與服務端程序二、用戶賬號管理三、MySQL 客戶端命令3.1 命令類型?3.2 使用模式?3.3 常用選項?3.4 提示符定制?四、mysqladmin管理命令??五、服務端配置?5.1 配置文件???5.2 Socket 通信配置??六、最佳實踐總結免費個人運維知識庫&#xff0c;歡迎…

自動化項目日報生成工具測評與選型:如何匹配團隊日報管理需求

引言在項目管理場景中&#xff0c;手動撰寫日報常面臨多重效率瓶頸&#xff1a;任務數據分散在協作群、Excel 表格、項目看板等多個平臺&#xff0c;匯總時需反復核對&#xff1b;不同成員日報格式不統一&#xff0c;管理層整合分析耗時&#xff1b;任務進度與日報信息不同步&a…