spring仅支持单例bean的setter/字段注入循环依赖,通过三级缓存(singletonobjects、earlysingletonobjects、singletonfactories)提前暴露早期引用解决;不支持构造器注入、prototype作用域及跨容器循环依赖。

Spring 中的循环依赖指的是两个或多个 Bean 相互依赖,形成闭环引用,比如 A 依赖 B,B 又依赖 A。Spring 默认支持单例 Bean 的**构造器注入以外的循环依赖**(主要是 setter 注入或字段注入),但不支持构造器注入的循环依赖——因为构造实例前无法提前暴露未初始化的对象。
循环依赖是怎么产生的?
常见触发场景包括:
- 相互引用的 Service 层 Bean:ServiceA 调用 ServiceB 的方法,ServiceB 又调用 ServiceA 的方法,且都通过 @Autowired 字段或 setter 注入;
- 父子上下文误配:父容器和子容器中定义了同名 Bean,子容器尝试从父容器获取时引发间接依赖链混乱;
- 使用 @PostConstruct 或 InitializingBean 初始化逻辑中主动调用另一个尚未完全初始化的 Bean,虽不属典型“注入级”循环,但运行时会抛出 BeanCurrentlyInCreationException;
- 构造器注入闭环:A 的构造器要求 B,B 的构造器又要求 A —— Spring 无法解决,直接启动失败。
Spring 是如何解决(部分)循环依赖的?
Spring 通过三级缓存机制,在单例 Bean 创建过程中提前暴露“早期引用”来打破依赖闭环:
- 一级缓存(singletonObjects):存放完全初始化好的单例 Bean;
- 二级缓存(earlySingletonObjects):存放已实例化但未初始化(未执行 setter、@PostConstruct 等)的 Bean 对象;
- 三级缓存(singletonFactories):存放 ObjectFactory,用于按需创建早期引用(支持 AOP 代理对象的延迟生成)。
当 A 正在创建时,Spring 先将 A 的 ObjectFactory 放入三级缓存;A 在填充属性时发现依赖 B,于是开始创建 B;B 创建过程中又依赖 A,此时 Spring 从三级缓存拿到 ObjectFactory,生成早期 A 引用(可能是原始对象,也可能是代理对象),放入二级缓存并返回给 B —— 这样 B 就能继续初始化,之后 A 也能完成自身初始化。
哪些情况 Spring 无法解决?怎么应对?
以下情形 Spring 无能为力,需人工干预:
- 构造器注入循环:改用 setter 或字段注入;或拆分逻辑,引入第三方协调者(如事件驱动、命令模式);
-
prototype 作用域 Bean 循环依赖:Spring 不缓存 prototype 实例,每次 getBean 都新建,无法提前暴露;应避免 prototype Bean 直接相互注入,改用 ObjectProvider
或 ApplicationContext 延迟获取; - 依赖查找发生在初始化后阶段(如 @PostConstruct 方法内调用另一个 Bean):确保被调用 Bean 已就绪,或改用 ApplicationRunner / CommandLineRunner 延迟到上下文刷新完成后执行;
- 跨容器(如 WebApplicationContext 和 Root ApplicationContext)循环引用:检查包扫描范围与 Bean 定义位置,避免重复定义,优先使用依赖注入而非手动 getBean。
实用建议与排查技巧
遇到循环依赖报错(如 “Requested bean is currently in creation”),可这样处理:
- 看异常栈中的 Bean 名称,定位哪两个(或多个)Bean 卷入循环;
- 检查它们的注入方式:是否全是构造器注入?尝试改为 @Autowired 字段 + @Lazy(对非构造注入生效);
- 用 @Lazy 标记其中一个依赖,让其加载延迟到首次访问时,打破创建时依赖链;
- 启用 debug 日志:logging.level.org.springframework.beans=DEBUG,观察 Bean 创建顺序与缓存命中过程;
- 考虑是否真的需要双向依赖——多数时候是设计信号:把共用逻辑提取成独立 Service,或用事件(ApplicationEvent)解耦。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











