当前位置: 首页 > news >正文

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.dtcms.com/a/339951.html

相关文章:

  • JB4-9-任务调度
  • 网络通信基础:从数据链路层到传输层
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘paramiko’问题
  • Leetcode 3652. Best Time to Buy and Sell Stock using Strategy
  • 【20250819】mathtype的使用
  • Sklearn 机器学习 房价预估 计算房价和特征值的相关性
  • 差分约束系统SPFA判负环
  • 【自动驾驶】8月 端到端自动驾驶算法论文(arxiv20250819)
  • 决策树1.1
  • 设计模式笔记_行为型_解释器模式
  • 集成电路学习:什么是Thresholding阈值处理
  • PowerBI VS FineBI VS QuickBI实现帕累托分析
  • Go 并发入门:从 goroutine 到 worker pool
  • 用 C++ 构建高性能测试框架:从原型到生产实战指南
  • Python 项目里的数据预处理工作(数据清洗步骤与实战案例详解)
  • 在线客服系统访客表的设计与实现-增加最新消息字段
  • Task01: CAMEL环境配置及第一个Agent
  • Kubernetes Ingress实战:从环境搭建到应用案例
  • C语言基础:(十九)数据在内存中的存储
  • Java线程池参数配置的坑:`corePoolSize=0` + `LinkedBlockingQueue`直接变成串行执行
  • Python爬虫第二课:爬取HTML静态网页之《某某小说》 小说章节和内容完整版
  • 智驾-AEB
  • 羟氯喹通过抑制抗磷脂综合征诱导的绒毛外滋养细胞过度自噬
  • 【模版匹配】基于深度学习
  • 洛谷 P2834 纸币问题 3-普及-
  • 《当 AI 学会 “思考”:大语言模型的逻辑能力进化与隐忧》
  • centos 总有new mail出现原因
  • [论文阅读] 软件工程 - 用户体验 | VR应用的无障碍性困局:基于Meta和Steam商店评论的深度剖析
  • 多幅图片拼接算法系统
  • FIFO通讯速率> 30MB/s,CH346保障FPGA与PC的高速通道稳定高效