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…