资讯详情

Spring Boot 3 启动报循环依赖异常,为什么以前能跑的代码现在起不来

📅 2026/11/5 5:27:20 | 华诺云谱 👁 阅读
Spring Boot 3 启动报循环依赖异常,为什么以前能跑的代码现在起不来
本文摘要升级 Spring Boot 3 后启动即报循环依赖常被误判为新版本的坑。其实默认禁止注入成环从 2.6 就已生效需先复现报错并拆解注入链。一、问题与结论复现环境JDK 17、Spring Boot 3.2.5、Maven 3.9可选 Docker 中的mysql:8.0。结论先给这不是 Spring Boot 3 新增的限制。决定能否启动的是spring.main.allow-circular-references它的默认值在 Spring Boot 2.6 就已由true改为false从 2.5 及更早版本一路升到 3.2.5 的项目第一次跑在新默认值上就会在这里失败。日志没有走到Started DemoApplication直接抛出失败块下面按 Spring Boot 3.2.x 失败分析器的固定格式整理不同小版本的措辞与堆栈层级可能有差异未逐一比对*************************** APPLICATION FAILED TO START *************************** Description: The bean inventoryClient cannot be obtained - there is circular bean dependency Action: Consider redesigning the dependencies so that no beans are dependent on each other.Caused by里能看到内层异常BeanCurrentlyInCreationException: Error creating bean with name inventoryClient: requested bean is currently in creation: Is there an unresolvable circular reference?它点出了环上的另一个节点。若项目连着 Docker 里的mysql:8.0这条报错发生在数据源初始化之前可先排除数据库与网络因素。二、排查与选择依据排查目标是拿到环路径再决定打补丁还是改结构。读失败块bean 名就是环上节点两个名字连起来即orderService → inventoryClient → orderService。沿注入点回溯grep -rn Autowired src/main/java找字段注入构造器注入则看参数表里是否互相出现。查历史配置git log -p -- application.yml | grep allow-circular老项目常在升级时顺手清掉这行于是新默认值生效。排除干扰项spring.main.allow-bean-definition-overriding是另一个开关只管 bean 覆盖写它解决不了环。替代方案与取舍方案选择条件代价边界spring.main.allow-circular-referencestrue升级过渡期先恢复启动掩盖依赖环失败后移仅临时止血需记技术债Lazy打在注入点单点小环改动最小引入代理时序更隐蔽构造器环要注在构造参数上重构消除依赖环环在自己代码里准备长期维护改动面与回归成本高三方 starter 内部成环时不归你改不该用开关的情况环来自业务代码互相调用且还会继续增长开关只是把启动期失败推迟到运行期届时定位成本更高。三、关键原理单例创建走DefaultSingletonBeanRegistry的三级缓存singletonObjects、earlySingletonObjects、singletonFactories。字段或 setter 注入之所以能成环是 A 建到一半时把早期引用放进缓存B 拿到它继续初始化需要 AOP 时由getEarlyBeanReference提前产出代理。把allow-circular-references设为false后getSingleton在singletonsCurrentlyInCreation中发现重入立即抛BeanCurrentlyInCreationException。问题因此提前到启动期暴露而不是等某个业务请求踩到未初始化的字段才炸。2.6 改默认值的用意是让依赖显式化隐式环让初始化顺序依赖注入时机配合代理时还会出现“注入的是原始对象而非最终包装对象”这类难复现的问题。代价是升级时必须处理存量环本文的失败正是这笔账。构造器注入的环在任何配置下都撑不住对象尚未构造完成就互相等待全局开关对它无效Lazy必须落在构造参数上或者拆分职责。四、可运行示例环境与输入JDK 17.0.x、Spring Boot 3.2.5传递依赖spring-context6.1.x、Maven 3.9.x两个互相注入的单例。parentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion3.2.5/version/parentdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter/artifactId/dependency/dependenciesServicepublicclassOrderService{AutowiredprivateInventoryClientinventoryClient;}ServicepublicclassInventoryClient{AutowiredprivateOrderServiceorderService;}SpringBootApplicationpublicclassDemoApplication{publicstaticvoidmain(String[]args){SpringApplication.run(DemoApplication.class,args);}}操作步骤mvn-q-DskipTestspackagejava-jartarget/demo-0.0.1-SNAPSHOT.jar实际输出APPLICATION FAILED TO START与The bean inventoryClient cannot be obtained - there is circular bean dependency进程退出码非 0。预期输出写入下面配置后重新执行日志出现Started DemoApplication in ... seconds进程正常驻留。spring:main:allow-circular-references:true常见失败属性写成spring.allow-circular-references: true少了main.前缀配置被静默忽略报错原样复现改回正确前缀即可。更稳的做法是在注入点打断环ServicepublicclassOrderService{privatefinalInventoryClientinventoryClient;publicOrderService(LazyInventoryClientinventoryClient){this.inventoryClientinventoryClient;}}五、验证结果与边界同一份代码、同一次升级修复前后对比观察项修复前修复后启动结果失败退出码非 0Started DemoApplication报错位置上下文刷新期数据源之前无报错依赖关系环被启动期校验拦下开关放行或Lazy打断该开关只决定启动期是否校验成环不改变运行期的注入结果修复前后启动耗时无实质差异。修复后建议在启动日志里检索circular应无匹配并补一条覆盖该 bean 的集成测试。适用边界构造器环加开关无效必须用Lazy构造参数或拆分职责。环里含Transactional/CGLIB 代理的 bean 时即使放行也可能拿到原始对象而非代理对象仍需重构。三方 starter 内部成环开关是成本最低的过渡手段但要在项目文档里记下这笔债别把它当长期方案。参考资料Spring Boot 3.2.5 参考文档Spring Application 属性
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑