Spring 6 循环依赖与早期代理:三级缓存的真实边界
Spring 6 循环依赖与早期代理:三级缓存的真实边界
基线:Spring Framework 6、Spring Boot 3。本文解释一个具体代理不一致案例,不把结论推广为所有 Bean 的普遍行为。
1. 问题模型
典型循环依赖:
A -> B -> Repository -> A
如果依赖注入发生在 Bean 完整初始化之前,Spring 可能通过三级缓存提前暴露引用。后续 Bean 初始化还可能经过 BeanPostProcessor 创建代理,于是出现:
Early Reference != Final Bean
2. 三级缓存能做什么
三级缓存中的 ObjectFactory 可以提供早期引用。实现 SmartInstantiationAwareBeanPostProcessor 的后处理器有机会通过 getEarlyBeanReference 参与早期代理。
AbstractAutoProxyCreator 会记录 early proxy reference,避免同一个自动代理在初始化后重复创建。但不是每一个 BeanPostProcessor 都参与 early reference。
普通后处理器若只在 postProcessAfterInitialization 阶段包装 Bean,就可能造成早期引用与最终对象身份不一致。具体是否发生,取决于 Spring 版本、后处理器顺序和代理类型。
3. Spring Boot 3 的处理建议
Spring Boot 2.6 起循环引用默认更严格。Boot 3 项目应优先拆除循环依赖,不要用 spring.main.allow-circular-references=true 作为默认修复。
推荐顺序:
- 拆分服务和 Repository 的职责;
- 使用事件或接口反转依赖方向;
- 对确实需要延迟创建的依赖使用 @Lazy,并评估生命周期;
- 检查事务、缓存、异步、异常翻译和自定义后处理器是否创建代理。
4. 排障方法
记录实际 Spring Framework 版本;查看 Bean 的代理类型和 Advisor;分别跟踪 getEarlyBeanReference 与 postProcessAfterInitialization;确认是否存在多个后处理器重复包装;检查 allowCircularReferences 和 BeanDefinition 顺序。
三级缓存不是“所有循环依赖都能解决”的保证。它只是容器在特定生命周期阶段提供早期引用的机制,最终仍需要一个清晰且可维护的依赖图。