Spring循环依赖源码调试详解,用两级缓存代替三级缓存

Spring循环依赖源码详解,改用两级缓存并实验

背景

最近一直在研究Spring的循环依赖,发现好像两级缓存也能解决循环依赖。

关于为何使用三级缓存,大致有两个原因

  1. 对于AOP的类型,保证Bean生命周期的顺序
    对于有AOP代理增强的类型,如果没有循环依赖,那么AOP的增强逻辑的执行点在:
无循环依赖:
java 复制代码
    Container->>Bean: 1. 实例化(constructor)
    Container->>Bean: 2. 属性注入(populate)
    Container->>Processor: 3. 调用postProcessAfterInitialization()
    Processor->>Processor: 4. 创建代理(wrapIfNecessary)
    Processor-->>Container: 5. 返回代理对象

在第4步,也就是初始化后置处理器postProcessAfterInitialization

实际代理包装在BeanPostProcessor子类AbstractAutoProxyCreator类中:

java 复制代码
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean); // 标记为提前代理
    return wrapIfNecessary(bean, beanName, cacheKey); // 创建代理
}
有循环依赖
java 复制代码
    Container->>Bean: 1. 实例化(半成品)
    Container->>EarlyCache: 2. 存入singletonFactories
    Container->>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方法进行真正的创建,多次调用会产生多个实例,这里可以及时创建实例,不必等到延迟加载,就解决了

java 复制代码
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

改用两级缓存

(针对单例循环setter场景,修改spring源码,三级缓存改为两级缓存)

java 复制代码
/**
	 * 修改: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());// 确保二级缓存不会存在相同的bean
				this.registeredSingletons.add(beanName);
			}
		}
	}

这里直接把三级缓存注释,在实例化完成后直接生成代理对象

创建测试类
java 复制代码
@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;
	}
}
java 复制代码
@Component
public class MyServiceImpl implements MyService{



	@Autowired
	MyServiceImpl2 myServiceImpl2;

	public MyServiceImpl2 getMyServiceImpl2() {
		return myServiceImpl2;
	}

	public void setMyServiceImpl2(MyServiceImpl2 myServiceImpl2) {
		this.myServiceImpl2 = myServiceImpl2;
	}

	public void eat(){
		System.out.println("吃饭服务");
	}

	@Override
	public void mainMethod() {
		eat();
	}
}
java 复制代码
@Component
public class MyServiceImpl2 {

	@Autowired
	MyServiceImpl myService;

	public MyServiceImpl getMyService() {
		return myService;
	}

	public void setMyService(MyServiceImpl myService) {
		this.myService = myService;
	}
}
java 复制代码
@Component
public class MyServiceImpl3 {
	@Autowired
	MyServiceImpl myService;

	public MyServiceImpl getMyService() {
		return myService;
	}

	public void setMyService(MyServiceImpl myService) {
		this.myService = myService;
	}
}
java 复制代码
MyServiceImpl ->myServiceImpl2
MyServiceImpl2 -> MyServiceImpl 
MyServiceImpl3 ->MyServiceImpl 
启动容器
java 复制代码
		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();

输出结果:

java 复制代码
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都是单例的,代理类也正常,所以两级缓存可以解决循环依赖,在有代理的情况下
相关推荐
寒士obj15 分钟前
Spring容器Bean的创建流程
java·后端·spring
掉鱼的猫27 分钟前
Spring AOP 与 Solon AOP 有什么区别?
java·spring
不是光头 强40 分钟前
axure chrome 浏览器插件的使用
java·chrome
笨蛋不要掉眼泪1 小时前
Spring Boot集成腾讯云人脸识别实现智能小区门禁系统
java·数据库·spring boot
桃源学社(接毕设)1 小时前
云计算下数据隐私保护系统的设计与实现(LW+源码+讲解+部署)
java·云计算·毕业设计·swing·隐私保护
用户0332126663671 小时前
Java 将 Excel 转换为 HTML:解锁数据在线展示的无限可能
java·excel
字节跳跃者2 小时前
SpringBoot + MinIO + kkFile 实现文件预览,这样操作更安全!
java·后端·程序员
似水流年流不尽思念2 小时前
Spring 的声明式事务在多线程的场景当中会失效,该怎么解决呢?
后端·spring·面试
天天摸鱼的java工程师2 小时前
OpenFeign 首次调用卡 3 秒?八年老开发扒透 5 个坑,实战优化到 100ms
java·后端·面试
whitepure2 小时前
万字详解Java集合
java·后端