新闻详情

新闻详情

首页 / 资讯中心 / 详情

构造器注入一启动就报 BeanCurrentlyInCreationException:三级缓存救不了的场景,我画完图才看懂

发布时间:2026/10/1 15:33:24来源:尧图网络
构造器注入一启动就报 BeanCurrentlyInCreationException:三级缓存救不了的场景,我画完图才看懂
title: 构造器注入一启动就报 BeanCurrentlyInCreationException三级缓存救不了的场景我画完图才看懂date: 2026-10-01tags: [Spring, 循环依赖, 三级缓存, AOP, 源码解析, Java]2025 年初我们做订单服务重构两个类互相依赖OrderService依赖InventoryService做扣减InventoryService又要回调OrderService更新状态。同事为了图省事全用了构造器注入服务一启动就抛BeanCurrentlyInCreationException。他把这两个类改成 Autowired 字段注入问题消失了但留言问我不是说 Spring 有三级缓存能解决循环依赖吗为什么构造器注入就不行这个问题我当年也被问倒过。后来把AbstractAutowireCapableBeanFactory的doCreateBean源码从头到尾走了一遍才真正理解三级缓存的边界它解决的是实例化之后、初始化之前的依赖暴露问题而构造器注入的依赖发生在实例化内部连先造出半成品的机会都没有。一、事故现场两个类互相要谁也生不出来最小复现代码Service public class OrderService { private final InventoryService inventoryService; // 构造器注入必须先拿到 InventoryService 才能实例化自己 public OrderService(InventoryService inventoryService) { this.inventoryService inventoryService; } public void deductAndComplete(Long orderId) { inventoryService.deduct(orderId); } } Service public class InventoryService { private final OrderService orderService; public InventoryService(OrderService orderService) { this.orderService orderService; } public void deduct(Long orderId) { orderService.markCompleted(orderId); } }启动时 Spring 的执行顺序// Spring 创建 bean 的主干逻辑简化 protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) { // 第 1 步实例化 —— 调用构造器此时还没进三级缓存 BeanWrapper instanceWrapper createBeanInstance(beanName, mbd, args); Object bean instanceWrapper.getWrappedInstance(); // 第 2 步把半成品放进三级缓存实例化之后才轮到这里 boolean earlySingletonExposure (mbd.isSingleton() this.allowCircularReferences isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { addSingletonFactory(beanName, () - getEarlyBeanReference(beanName, mbd, bean)); } // 第 3 步populateBean 注入属性、initializeBean 执行初始化 populateBean(beanName, mbd, instanceWrapper); }关键在第 1 步和第 2 步的先后顺序。创建OrderService时要先执行它的构造器构造器参数需要InventoryService于是 Spring 转头去创建InventoryService而InventoryService的构造器又需要OrderService——此时OrderService连实例都没 new 出来三级缓存里没有任何可以给对方的东西。Spring 检测到OrderService处于创建中状态又拿不到早期引用只能抛异常终止启动。我们当时的报错栈里有一行Requested bean is currently in creation指向的正是DefaultSingletonBeanRegistry里的beforeSingletonCreation校验。二、三级缓存到底存了什么字段定义在DefaultSingletonBeanRegistrypublic class DefaultSingletonBeanRegistry extends SimpleAliasRegistry { // 一级缓存成品 beankey 是 beanName private final MapString, Object singletonObjects new ConcurrentHashMap(256); // 二级缓存提前暴露的半成品实例化了但没填充属性 private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16); // 三级缓存能产出半成品的工厂 private final MapString, ObjectFactory? singletonFactories new HashMap(16); }逐行解释- 一级缓存是所有单例 bean 的最终归宿日常getBean命中的就是它。- 二级缓存存的是提前曝光的引用bean 实例化完成、属性还没注入时的原始对象。- 三级缓存存的是 ObjectFactory调用它才会得到可能被 AOP 包装的早期引用结果会晋升到二级缓存同时三级缓存里的工厂被移除。查找逻辑在getSingletonprotected Object getSingleton(String beanName, boolean allowEarlyReference) { // 先查一级缓存成品直接返回 Object singletonObject this.singletonObjects.get(beanName); if (singletonObject null isSingletonCurrentlyInCreation(beanName)) { // 一级没有且该 bean 正在创建中才继续查早期引用 singletonObject this.earlySingletonObjects.get(beanName); if (singletonObject null allowEarlyReference) { // 最后查三级缓存工厂 ObjectFactory? singletonFactory this.singletonFactories.get(beanName); if (singletonFactory ! null) { singletonObject singletonFactory.getObject(); // 工厂产出的对象晋升到二级缓存 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } return singletonObject; }回到事故场景如果两个类都改成字段注入setter 注入同理流程就变成——实例化OrderService不传参new 空壳成功→ 半成品进三级缓存 → 填充属性时发现要InventoryService→ 实例化InventoryService→ 它填属性时要OrderService→ 从三级缓存拿到OrderService的半成品引用 → 自己完成初始化 → 回过头OrderService拿到完整的InventoryService继续初始化。环就这么解开了。三、为什么要三级二级不够吗这是面试高频题但我不打算背标准答案我讲一下我自己推导的过程。假设只有二级缓存早期引用池。没有 AOP 时确实够用半成品放进去对方拿走引用最后初始化完成把成品覆盖进一级缓存。引用是同一个没问题。有 AOP 时问题来了。正常流程下代理对象是在初始化最后一步由BeanPostProcessor的postProcessAfterInitialization生成的成品池里放的应该是代理。但如果循环依赖发生在代理生成之前对方从二级缓存拿到的只能是原始对象——这个原始对象会被对方长期持有和最终容器里的代理对象不是同一个实例事务、缓存注解全部失效。三级缓存的 ObjectFactory 就是为此设计的protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject bean; for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessors()) { // AOP 的 AbstractAutoProxyCreator 实现了这个接口 // 提前在此生成代理而不是等到初始化之后 exposedObject bp.getEarlyBeanReference(exposedObject, beanName); } return exposedObject; }逐行看工厂被调用时遍历所有支持早期引用的后置处理器AOP 的AbstractAutoProxyCreator在这里提前判断这个 bean 是否需要代理需要就当场生成代理返回。这样对方拿到的引用和最终成品一致。那为什么不直接在实例化后统一生成代理放进二级缓存因为代理生成的前提条件比如 Transactional 的 Advisor 匹配、循环依赖是否存在在早期阶段并不完备Spring 的设计原则是没有循环依赖就按标准生命周期走有才提前。三级缓存的工厂相当于一个惰性开关没有环工厂永远不被调用代理照常在初始化后生成有环工厂被触发代理提前生成。二级缓存方案无法表达这种按需提前。四、三个三级缓存救不了的场景我总结成三条硬边界画在白板上传给了团队构造器注入的循环依赖。依赖发生在实例化内部对象不存在没有半成品可暴露。解法是其中一个改成 setter 注入或者在依赖方加Lazy让 Spring 注入一个延迟代理。prototype 作用域的循环依赖。三级缓存只服务单例prototype 每次都新建Spring 不缓存也不追踪直接抛异常。跨层初始化顺序敏感的场景。比如 A 的 PostConstruct 里依赖 B 已完全初始化而 B 又要提前引用 A——提前暴露的 A 是半成品B 在回调里调用 A 的方法时字段可能还是 null。我们在库存服务里就撞过一次回调链上读了一个注入进来的配置对象因为走的是早期引用读到了 nullNPE 挂在启动后的第一次请求里比启动失败更难查。第三条是我观点最重的一条能自己解决的循环依赖别指望容器兜底。我当时推动团队把这两个服务之间的回调改成了事件发布ApplicationEventInventoryService扣减后发事件OrderService监听更新状态依赖方向变成单向环直接消失。改完之后启动快了大概 3 秒少了两次代理提前生成的开销更重要的是代码不再隐式依赖Spring 会帮我解环这个魔法。五、复盘真实数字事故影响订单服务启动失败发版阻塞 40 分钟根因构造器注入形成实例化期环三级缓存无能为力修复改为事件解耦消除双向依赖后续收益同类问题在该团队此后一年零复发排查成本画依赖图 15 分钟 读 doCreateBean 源码约 2 小时六、思考题你的项目里有多少个 Autowired 字段注入试着把其中一个改成构造器注入观察启动行为有什么变化——如果没报错说明这条依赖链上没有环如果报错了恭喜你提前发现了一处隐式循环依赖。欢迎在评论区聊聊你拆环的方法。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Qoder AI IDE 完全上手:安装配置、Credits计费与高效开发实战 2026/10/1 18:44:55

Qoder AI IDE 完全上手:安装配置、Credits计费与高效开发实战

Qoder 这段时间在开发者圈子里讨论度挺高,特别是前端和全栈方向的朋友,很多从 Codex 或 Cursor 转过来的。我自己的主力编辑器从 VS Code 切到 Qoder 已经跑了两个多月,中间踩过不少坑,也摸清了它那套 credits 和模型调度的脾气。…

阅读更多 →
Hindsight 智能体记忆:MCP 协议与 Docker 部署实战 2026/10/1 18:44:42

Hindsight 智能体记忆:MCP 协议与 Docker 部署实战

1. 从“hindsight”这个词说起:为什么它值得单独拿出来聊 第一次看到“hindsight”作为项目名,我脑子里蹦出来的不是词典释义,而是自己踩过的一个坑。早些年做对话系统,用户问“我上周说的那个偏好还算数吗”,系统一脸…

阅读更多 →
AI-Native落地必读:企业知识库与RAG检索增强的工程实践 2026/10/1 18:44:42

AI-Native落地必读:企业知识库与RAG检索增强的工程实践

最近和几个做企业数字化的朋友聊天,大家嘴里都挂着AI-Native,可真问起来,十个里有八个卡在同一个地方:模型很强,落地很虚。模型能力已经不需要证明了,但要让AI真正接手业务,前提是AI得"懂&…

阅读更多 →
注意力机制聊天机器人实战:解压运行到调参优化全流程 2026/10/1 18:44:42

注意力机制聊天机器人实战:解压运行到调参优化全流程

简介:面向机器学习与自然语言处理学习者的中文聊天机器人课程设计项目,利用注意力机制增强模型对上下文的动态关注,适合希望快速构建端到端对话系统的学生与研究者参考。压缩包共22个文件,约58.86MB,包含4个ipynb代码笔…

阅读更多 →
BERTScore与Astra模型在法律AI中的可验证评估实践 2026/10/1 18:44:35

BERTScore与Astra模型在法律AI中的可验证评估实践

1. 项目概述:当AI建议在LinkedIn上“半技术”地冒充专家 最近刷LinkedIn时,你有没有被这类内容扎过眼?——标题写着《用BERTScore优化LLM微调流程》,点开正文却只有一张模糊的PyTorch截图、两行没注释的代码、三句“效果提升显著”…

阅读更多 →
Python第一次作业全攻略:从环境搭建到避坑指南 2026/10/1 18:44:35

Python第一次作业全攻略:从环境搭建到避坑指南

第一次作业,这四个字能把多少人的编程热情直接浇灭,我当了这么多年Python课程的助教,每年都能看到一批学生在命令行前面耗到凌晨,最后交上来的却是一份自己都没运行成功的代码。其实Python第一次作业考察的东西远没有你想的那么玄…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉