bigywater.tech
Spring · 6

循环依赖:三级缓存解决了什么,构造器注入为什么解决不了

循环依赖在 Spring 里什么时候能解决、什么时候不能?三级缓存到底给了谁一个半成品?为什么 Boot 默认把它关掉?循环加上代理会发生什么?这篇在 Spring Framework 7.0.9 上把六种情形逐个跑出来。

2 次
循环依赖加自定义 BPP 包装时,SA 与 SB 各被构造的次数(7.0.9 会重建而不是报错)
false
循环里 B 在 @PostConstruct 里看到的 A.ready(拿到的是半成品)
false / true
SpringApplication 的 allowCircularReferences 默认值与打开后的启动结果:失败 / 成功

setter 循环

实测 · 本机执行输出
    1. 构造 A
    2. 构造 B
    3. B.setA(拿到的 A 还没初始化完成)
    4. B.@PostConstruct(此时 A.ready=false)
    5. A.setB(B 已经构造且初始化完成)
    6. A.@PostConstruct
    a.b == b ? true;b.a == a ? true

能解开,代价是 B 初始化时看到的 A 还没有初始化完成。

构造器循环

实测 · 本机执行输出
    ctor → BeanCurrentlyInCreationException: Error creating bean with name 'ca': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?

没有半成品可拿,直接抛 BeanCurrentlyInCreationException。

Boot 默认

实测 · 本机执行输出
    allowCircularReferences=false:BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency?
    allowCircularReferences=true:启动成功

SpringApplication 默认禁止循环引用,需要显式打开。

循环加包装

实测 · 本机执行输出
    1. 构造 SA
    2. 构造 SB
    3. SB 注入 sa:原始对象
    4. SA 注入 SB(此时 SB 里的 sa=SA)
    5. 构造 SB
    6. 构造 SA
    7. SA 注入 SB(此时 SB 里的 sa=null)
    8. SB 注入 sa:代理
    isAllowRawInjectionDespiteWrapping=false;构造 SA 的次数 2,构造 SB 的次数 2
    启动成功。容器里的 sa 是代理?true;SB 里持有的 sa 是代理?true;两者是同一个对象?true

自定义 BPP 包装时 7.0.9 会把相关 Bean 重建一轮;Spring AOP 的自动代理创建器能提前暴露代理。

AOP 循环

实测 · 本机执行输出
    容器里的 PA 是代理?true;PB 持有的 pa 是代理?true;pb.pa.hello() = [切面]PA.hello;pb.pa 就是容器里的 PA?true

切面在循环依赖里依然生效。

@Lazy

实测 · 本机执行输出
    1. 构造 LA(拿到的 LB 是一个懒加载代理)
    2. 构造 LB

构造器参数标 @Lazy 注入代理,可以绕过构造器循环。

分步演示

循环依赖与早期引用
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。前两、四、五、六项用不带 Boot 的 Spring Framework 7.0.9 的 AnnotationConfigApplicationContext,第三项用 SpringApplication(WebApplicationType.NONE);AOP 用 spring-boot-starter-aspectj 带来的 AspectJ
    • 没有验证:老版本里第四项抛的异常(这是记忆,没有在老版本上跑);Spring 7.0.9 重建 SA、SB 的具体机制(只观察到了现象:时间线上各构造 2 次);@Async 的代理在循环里的行为;三级缓存的内部结构(没有读源码,说的是外部可观察的行为);spring.main.allow-circular-references 通过配置文件设置的效果
    京ICP备2026067502号-1京公网安备11010502063487号