spring仅支持单例bean的setter/字段注入循环依赖,通过三级缓存提前暴露半成品bean来解决;构造器注入、原型作用域及含@async等增强的bean因无法提前暴露或不入缓存而抛出beancurrentlyincreationexception。

Spring 并不“处理”所有循环依赖,而是有选择地支持特定场景下的闭环依赖——它只对单例 Bean 的 setter/字段注入型循环依赖提供自动解法;一旦涉及构造器注入、原型作用域或初始化阶段强耦合(如 @Async、AOP 代理、Liquibase 初始化等),就会抛出 BeanCurrentlyInCreationException。
为什么有些循环依赖能启动,有些却报错?
关键看三点:作用域、注入方式、Bean 初始化时机。
- 单例 + 字段或 setter 注入 → Spring 用三级缓存(singletonFactories → earlySingletonObjects → singletonObjects)提前暴露未初始化完的 Bean 实例,实现“先建壳、后填属性”,从而破环
- 构造器注入 → 容器必须一次性把所有依赖准备好才能调用构造方法,无法提前暴露,直接失败
- 原型(prototype)Bean → 每次 getBean 都新建,不进单例池,三级缓存不生效,无法解环
- @Async、@Transactional、@EventListener 等增强类 → 需要代理对象,而代理生成常发生在 initializeBean 阶段;若此时依赖链已形成原始引用,就会出现“注入了 raw version,但最终被 wrap”的提示
常见触发报错的典型组合
不是所有循环引用都会崩溃,但以下情况极易命中异常:
- A 服务中 @Autowired B,B 中 @Autowired C,C 中又 @Autowired A —— 表面三层,本质闭环
- 某个 Bean 同时被 Liquibase 和 JPA EntityManagerFactory 依赖,而 dataSource 又依赖 Security 配置或 OAuth2 组件 → 形成跨模块隐式依赖链
- 使用 @Async 的 Service 类参与循环引用 → 代理创建时机与依赖注入顺序冲突
- 多模块集成时包扫描范围过大(如主应用 scan 到批处理模块的配置类),导致 Configuration 类之间隐式依赖闭合
真正有效的解决方向
别指望 Spring 自动兜底,需从设计和配置层面主动破环:
- 优先拆依赖:把共用逻辑抽成独立 Service 或工具类,让 A 和 B 都依赖它,而非彼此
- 改构造注入为字段注入(仅限单例):避免构造阶段强绑定,给 Spring 留出缓存介入空间
- 加 @Lazy:在其中一个依赖上标注,延迟其初始化,打断创建时序闭环
- 精准控制包扫描:主应用 exclude 批处理模块的配置类;或用 @Import 显式导入,避免自动发现引发意外依赖
- 分离配置生命周期:把 DataSource、Liquibase、Security 等基础设施配置放在独立 @Configuration 类中,并用 @DependsOn 明确初始化顺序
怎么快速定位是哪几个 Bean 在打架?
看异常堆栈末尾的 bean 名称链路,例如:
OAuth2AuthorizationServerConfiguration → tokenStore → stellantisroiApplication → batchExecuter → batchConfigRepository → entityManager → entityManagerFactory → jmix_Liquibase → dataSource顺着箭头反向追踪,找到第一个重复出现的 Bean(比如 dataSource 又被 Liquibase 和 entityManagerFactory 同时持有),那就是循环支点。再检查这些类的 @Autowired 字段和构造函数参数,就能锁定问题源头。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











