spring boot循环依赖本质是bean初始化时互相等待导致创建顺序无法确定,构造器注入在2.6+版本必报错,字段注入默认被禁用;解决需用@lazy、方法传参、提取公共service或事件解耦,而非开启allow-circular-references。

Spring Boot 中循环依赖问题,本质是两个或多个 Bean 在初始化过程中互相等待对方完成,导致容器无法确定创建顺序。它不是代码写错了,而是设计上出现了职责纠缠——比如 UserService 调用 OrderService 的方法,而 OrderService 又反过来调用 UserService 的某个校验逻辑,形成闭环。
为什么一启动就报错?关键看注入方式和版本
构造器注入的循环依赖在任何 Spring Boot 2.6+ 版本中都会直接失败,因为构造函数必须拿到完整依赖才能实例化对象,两边卡死谁也动不了。字段注入(@Autowired)虽然曾被三级缓存“兜底”,但从 Spring Boot 2.6 开始,默认禁止所有循环引用,报错信息会明确画出依赖环:
- ┌─────┐
- | userService ↑↓ | orderService └─────┘
这不是 Spring 不行了,而是官方主动收紧策略:循环依赖是设计坏味道,不该靠容器“擦屁股”。
快速定位:从日志里一眼看出谁在成环
错误日志里的 ASCII 图形就是线索。顺着箭头方向逐级展开,比如看到 shiroFilter → securityManager → userRealm → sysUserService → sysRoleService → sysUserService,说明最后一步又绕回去了。重点检查:
- 每个类里 @Autowired 或构造参数中是否直接引用了环内其他 Bean
- 是否用了 @PostConstruct 或初始化方法,在 Bean 尚未完全创建时就触发了对方调用
- 是否在配置类、拦截器、监听器中隐式引入了跨层依赖
真正能落地的解决办法
关开关(spring.main.allow-circular-references=true)只是掩耳盗铃,下面这些才是生产环境可用的方案:
- @Lazy 注入:在其中一个依赖上加 @Lazy,让 Spring 塞一个代理对象进去,真用到时才创建目标 Bean。适合构造器注入场景,改动小、见效快
- 改为方法参数传入:把原本类级别的依赖,改成业务方法的入参。例如 userRoleService.assignRole(userId, roleService),彻底解除静态依赖
- 提取公共服务:当 A 和 B 都需要对方能力时,把共用逻辑抽成第三个 Service C,A 和 B 都只依赖 C
- 用 ApplicationEvent 解耦:A 完成操作后发事件,B 监听并响应,避免直接持有引用
怎么避免以后再出现
循环依赖往往不是突然冒出来的,而是随着功能叠加慢慢长出来的。建议在团队协作中:
- 新模块优先用构造器注入,强制暴露依赖关系
- 定期用 IDEA 的 “Analyze Dependencies” 或 Maven 插件扫描依赖图谱
- 在 CI 流程中加入启动检查,一旦发现循环依赖立即阻断构建











