首页/新闻资讯/正文详情

Spring 循环依赖:三级缓存背得再熟,不如看这 3 个线上坑

发布时间:2026/9/25 6:44:31 来源:云帆数科 栏目:资讯中心
Spring 循环依赖:三级缓存背得再熟,不如看这 3 个线上坑
循环依赖是 Spring 项目里一不留神就会碰到的问题尤其在多人开发的项目里两个人各写各的 Service一个要调 A一个要调 B合代码时谁也没注意互相引了——启动直接红字BeanCurrentlyInCreationException: Error creating bean with name a: Requested bean is currently in creation: Is there an unresolvable circular reference?我最常碰到的还不是简单的 A 依赖 B、B 依赖 A 两两互指而是链式的A 注入 BB 注入 CC 又注入 A——三个类串成一个环启动必崩。下面从原理到解法讲透附能跑的代码。一、先 30 秒理解什么是循环依赖就是 A 依赖 BB 依赖 CC 又依赖 A对象创建时形成闭环ServicepublicclassA{privatefinalBb;// 依赖 B}ServicepublicclassB{privatefinalCc;// 依赖 C}ServicepublicclassC{privatefinalAa;// 又依赖回 A成环}注意这不是函数递归调用那种是死循环是对象创建时的相互依赖。环上有几个 bean 都叫循环依赖链式三个以上比两两互指更常见。二、补充知识Bean 的三种注入方式要理解循环依赖为什么难解先看 Spring 注入 Bean 的三种方式注入方式写法优点缺点构造器注入推荐构造器参数 /RequiredArgsConstructor依赖明确、final 不可变、对象创建即完整官方推荐循环依赖时启动即崩实例化阶段就卡住字段注入Autowired private B b;代码最少依赖藏在字段里难测试、难发现循环官方不推荐setter 注入Autowired setB(B b)可选依赖友好创建后依赖可变不如构造器稳推荐默认用构造器注入——依赖一目了然、不可变、好测试。只有遇到循环依赖时才用Lazy破环坑 1 讲别为了省事全程字段注入。三、3 分钟理论Spring 靠三级缓存解决Spring 创建单例 Bean 分三步实例化createBeanInstance调构造器new 出对象填充属性populateBean注入依赖初始化initializeBeanAOP 代理、init 方法等循环依赖卡在第 1、2 步之间。Spring 的处理是提前暴露对象构造完、还没填充属性时就先放进缓存让别人拿。它有三层缓存缓存作用singletonFactories三级存对象工厂提前暴露的关键earlySingletonObjects二级存提前暴露的早期对象singletonObjects一级存初始化完成的成品 Bean流程是这样的A 实例化完把自己塞进三级缓存 → 填充属性发现要 B → 去创建 B → B 实例化完发现要 A → 一级没有、二级没有、三级有B 从三级缓存拿到 A 的引用 → B 顺利初始化完进一级缓存 → A 拿到 B也完成初始化。关键结论三级缓存的加入前提是对象已经调过构造器了——所以setter/字段注入能解决循环依赖构造器注入解决不了后者直接抛BeanCurrentlyInCreationException。理论就这么多面试够用了。下面是真正折磨人的 3 个实战坑。坑 1构造器注入的链式循环启动直接崩就是最开始的报错。现在流行构造器注入最规范但多人开发时链式循环一来启动必崩ServicepublicclassA{privatefinalBb;publicA(Bb){this.bb;}}ServicepublicclassB{privatefinalCc;publicB(Cc){this.cc;}}ServicepublicclassC{privatefinalAa;// 依赖回 A成环publicC(Aa){this.aa;}}启动即报BeanCurrentlyInCreationException。解法 1Lazy推荐改动最小——打破环上的任意一条边ServicepublicclassC{privatefinalAa;publicC(LazyAa){this.aa;}// 延迟解析环被打破}Lazy让 Spring 先注入一个代理对象真正调用时才去拿目标 Bean。解法 2换成字段注入不推荐用它掩盖问题ServicepublicclassC{AutowiredprivateAa;// 字段注入走 setter 流程能过}字段注入本质是填充属性阶段注入走了三级缓存路径所以能解。但正如第二节说的字段注入是官方不推荐的方式——用它破环属于能跑但丑根治看坑 3。坑 2循环依赖 事务/切面方法静默失效这个坑最阴启动不报错业务悄悄出错。场景A 有Transactional方法B 依赖 A。因为循环依赖B 在早期阶段拿到的 A不是最终完成 AOP 代理的成品而是提前暴露的早期对象ServicepublicclassA{AutowiredprivateBb;TransactionalpublicvoiddoSth(){}}ServicepublicclassB{AutowiredprivateAa;// 拿到的可能是早期对象publicvoidcall(){a.doSth();// 事务可能没生效}}结果就是doSth()里要么事务边界不对要么依赖切面的逻辑如Async、自定义 AOP表现诡异排查起来极其费时——因为你单测、正常调用都对只有走这条循环链路时才坏。解法还是Lazy让 B 真正调用 A 时才解析保证拿到的是完整代理ServicepublicclassB{AutowiredLazyprivateAa;// 真正调用时才解析拿完整代理}教训看到循环依赖 事务/切面组合别能跑就行必须把代理问题一起验证否则上线就是事故。坑 3循环依赖往往是设计坏味道这是我最想强调的一点。两个或多个Service 互相注入八成是职责边界划错了。最常见的案例A 其实只需要 B 的某个动作却直接注入了 B 的整个能力。正确的重构方向按优先级抽公共依赖把互相调用的那部分逻辑下沉到独立的 Service两边都依赖它环就断了单向调用审视业务很多互相调用其实有一方是多余的事件解耦A 做完发 Spring Event / 用 MQB 监听处理彻底不互相持有// 重构后A 只依赖抽出来的 CommonService不再反向ServicepublicclassA{privatefinalCommonServicecommonService;// 单向}ServicepublicclassCommonService{// 谁都不回依赖 A环断了}Lazy是止痛药重构才是根治。如果项目里循环依赖超过 2 处先别急着Lazy想想是不是设计该调了。总结场景结论构造器注入循环依赖启动即崩用Lazy或改字段注入setter/字段注入循环依赖三级缓存能解但慎用循环依赖 事务/切面可能静默失效必须验证代理多处循环依赖重构边界抽公共 Service 或事件解耦你项目里遇到过最诡异的循环依赖是啥是启动崩还是业务悄悄错评论区聊聊。相关阅读Spring 事务失效的 11 个真实场景——和这篇的坑 2 正好能串起来都是代码看着对、运行出问题的典型。本文首发于公众号「今天写BUG了么」关注看更多

相关推荐

DP83848工业以太网PHY设计调试全攻略:从硬件到驱动
DP83848工业以太网PHY设计调试全攻略:从硬件到驱动

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 4:51:22

Swagger Codegen 生成 Java 客户端指南:解析 google-api-client 版 AnotherFakeApi 与 testSpecialTags 调用
Swagger Codegen 生成 Java 客户端指南:解析 google-api-client 版 AnotherFakeApi 与 testSpecialTags 调用

开发工具代码生成API设计 【免费下载链接】swagger-codegen swagger-codegen contains a template-driven engine to generate documentation, API clients and server stubs in different languages by parsing your OpenAPI / Swagger definition. 项目地址: http… · 2026/9/24 4:51:22

RenderDoc 功能测试与验证指南:从 ad-hoc 手测到自动化测试套件
RenderDoc 功能测试与验证指南:从 ad-hoc 手测到自动化测试套件

开发工具调试器图形学GPU 【免费下载链接】renderdoc RenderDoc is a stand-alone graphics debugging tool. 项目地址: https://gitcode.com/gh_mirrors/re/renderdoc 点击查看 免费下载 本指南面向 RenderDoc 的贡献者与二次开发者,系统说明在为 Rend… · 2026/9/24 4:51:16

金融场景下托管式智能体落地:Managed Agents API与MCP实践
金融场景下托管式智能体落地:Managed Agents API与MCP实践

1. 金融场景下 Managed Agents API 的落地思路拆解金融行业对自动化的态度一直很拧巴:一边是大量重复、规则明确的流程(对账、报表、合规检查、客户资料录入),一边是监管、审计、数据隔离这些硬约束,导致很多团队宁可手… · 2026/9/25 6:44:30

Atlas 300V 推理卡部署 YOLO 全流程指南:从 ONNX 到 OM 的工程实践
Atlas 300V 推理卡部署 YOLO 全流程指南:从 ONNX 到 OM 的工程实践

后台每隔两周就会收到类似的私信:Atlas 300V 24G 是运算加速卡吗?我手里有一块 Atlas,想把 YOLO 模型部署上去,但完全不知道从哪开始。每次看到这种问题,我都想起自己第一次把 Atlas 300V 插进服务器时的状态&#xff… · 2026/9/25 6:44:23

无感FOC滑模观测器原理推导与工程实现全解析
无感FOC滑模观测器原理推导与工程实现全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:44:23

Windows U盘拒绝访问的四层权限解析与实战修复
Windows U盘拒绝访问的四层权限解析与实战修复

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:44:23

.NET Core WebApi 文件上传下载避坑指南:从413到断点续传
.NET Core WebApi 文件上传下载避坑指南:从413到断点续传

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:44:23

六种调制仿真避坑指南:从QPSK星座图旋转到误码率对齐
六种调制仿真避坑指南:从QPSK星座图旋转到误码率对齐

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 6:44:23

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码