Spring循环依赖:为什么原型Bean和构造器依赖无解?三级缓存又为何而生?
一、什么是循环依赖一个“先有鸡还是先有蛋”的编程难题想象一下这个场景小明说“我的工作依赖小红完成的数据。”小红说“不行我得等小明先把框架搭好我才能开始处理数据。”两人互相等待工作永远无法开始——这就是现实中的“循环依赖”。简单来说就是A依赖B,B又依赖A。在Spring框架中循环依赖指的是两个或多个BeanSpring管理的对象在创建时相互依赖形成一个闭环。比如Service public class ServiceA { Autowired private ServiceB serviceB; // A依赖B } Service public class ServiceB { Autowired private ServiceA serviceA; // B又依赖A }Spring要创建ServiceA发现它需要ServiceB去创建ServiceB发现它又需要ServiceA……陷入死循环。二、循环依赖解决办法Spring通过三级缓存巧妙地解决了单例Bean通过Setter/Field注入的循环依赖问题。先看这个经典的生活比喻2.1 三级缓存是什么你可以把Spring容器想象成一个房屋建造公司它有三个“仓库”缓存级别仓库名称存放内容类比现实一级缓存singletonObjects完全初始化好的Bean完成实例化且初始化的对象装修完毕、可以入住的房子二级缓存earlySingletonObjects提前暴露的早期引用半成品完成实例化但未初始化的对象刚搭好框架、没装修的毛坯房三级缓存singletonFactories创建Bean的工厂对象房屋设计图纸施工队2.2 解决循环依赖的完整流程以A→B→A为例开始创建ASpring发现需要创建ServiceA先把它标记为“创建中”然后把A的工厂放入三级缓存存图纸。A依赖B开始填充A的属性发现需要ServiceB。转去创建B同样标记B为“创建中”把B的工厂放入三级缓存。B依赖A填充B的属性时发现需要ServiceA。关键一步Spring去缓存找A一级缓存成品房→ 没有二级缓存毛坯房→ 没有三级缓存图纸→ 找到A的工厂工厂执行生成A的早期引用一个代理对象或原始对象放入二级缓存并从三级缓存移除工厂。B完成创建B拿到了A的早期引用属性填充成功B创建完成放入一级缓存。A完成创建回到A的创建流程现在能拿到完整的B了A也创建完成放入一级缓存。整个过程就像两个施工队互相借用对方的“框架”先让房子能立起来再慢慢完善内部装修。三、为什么有些循环依赖Spring也无能为力3.1 原型BeanPrototype的循环依赖为什么不能解决根本原因原型Bean每次获取都是新实例Spring不缓存它们。想象一下单例Bean全村共用一口井缓存起来大家引用同一个原型Bean每家自己打一口新井每次getBean都新建当A原型依赖B原型时创建A1 → 需要B创建B1 → 需要A → 创建A2因为原型要新建A2又需要B → 创建B2 → 需要A → 创建A3……无限套娃直到栈溢出。Spring检测到这种场景会直接抛出BeanCurrentlyInCreationException。3.2 构造器注入的循环依赖为什么不能解决根本原因对象在构造完成前连“早期引用”都无法提供。Service public class ServiceA { private final ServiceB serviceB; // 构造器注入创建A的瞬间就必须有完整的B public ServiceA(ServiceB serviceB) { this.serviceB serviceB; } } Service public class ServiceB { private final ServiceA serviceA; public ServiceB(ServiceA serviceA) { this.serviceA serviceA; } }问题在于Setter/Field注入可以先创建对象实例调用默认构造器得到一个“空壳子”再慢慢填充属性。构造器注入必须在构造函数里就传入完整的依赖对象对象在诞生那一刻就必须是“完整的”。这就好比Setter注入先造出汽车框架再安装发动机、轮胎。构造器注入要求发动机、轮胎在汽车框架诞生前就必须准备好——但发动机又需要汽车框架才能造出来。Spring在创建A时连A的实例都还没产生构造函数没执行自然无法提供A的早期引用给B使用。如果想解决的话可以在构造器方法的参数上添加lazy注解延迟生成依赖的代理对象先完成实例化操作。四、深入思考二级缓存够用吗为什么需要三级4.1 如果只有二级缓存会怎样假设Spring只有一级缓存成品和二级缓存半成品// 伪代码逻辑 Object getBean(String name) { // 1. 从一级缓存找成品 Object bean singletonObjects.get(name); if (bean ! null) return bean; // 2. 从二级缓存找半成品 bean earlySingletonObjects.get(name); if (bean ! null) return bean; // 3. 创建新Bean bean createBean(name); // 创建后直接放入二级缓存 earlySingletonObjects.put(name, bean); // 填充属性可能触发循环依赖 populateBean(bean); // 初始化完成从二级缓存移除放入一级缓存 earlySingletonObjects.remove(name); singletonObjects.put(name, bean); return bean; }问题如果Bean需要AOP代理怎么办Spring AOP会在Bean初始化后生成代理对象替换原始对象。如果只有二级缓存A创建半成品放入二级缓存原始对象B依赖A从二级缓存拿到A的原始对象B创建完成A继续初始化被AOP增强生成代理对象结果B持有的是A的原始对象A最终是代理对象 →不一致4.2 三级缓存如何解决代理问题三级缓存存放的不是Bean实例而是ObjectFactory工厂。这个工厂的聪明之处在于延迟决策只有在真正被其他Bean依赖时才决定返回原始对象还是代理对象。保证一致性所有依赖方通过同一个工厂获取引用拿到的是同一个对象要么都是原始对象要么都是代理对象。// 三级缓存的核心逻辑 addSingletonFactory(beanName, () - { // 这个lambda在需要时才执行 Object earlyReference getEarlyBeanReference(beanName, bean); // 这里会判断是否需要AOP代理 return earlyReference; });关键优势解决代理一致性问题工厂在第一次被调用时如果发现Bean需要代理就生成代理对象否则返回原始对象。之后所有依赖方都拿到同一个引用。避免不必要的代理创建如果Bean没有被循环依赖工厂永远不会执行节省了创建代理的开销。处理复杂的代理逻辑支持Async、Transactional等基于代理的增强。五、面试官想听到的“点睛之笔”当面试官问“为什么用三级缓存而不是二级”时你可以这样回答“Spring设计三级缓存主要是为了优雅地处理AOP代理与循环依赖的兼容问题。二级缓存只能存储Bean的早期引用但无法处理这个引用应该是原始对象还是代理对象的‘决策延迟’问题。三级缓存通过ObjectFactory实现了‘用时才决策’的机制既保证了循环依赖中的对象一致性又避免了不必要的代理创建开销。”六、实际开发中的最佳实践优先使用构造器注入Lombok的RequiredArgsConstructor让循环依赖在编译期就暴露。避免循环依赖这是架构设计问题而不是技术炫技点。如果确实需要循环依赖使用Lazy延迟加载打破僵局Autowired Lazy private ServiceB serviceB;考虑使用事件驱动或观察者模式解耦相互调用的Bean。七、总结循环依赖Bean间相互等待创建的“死锁”问题。三级缓存Spring解决单例Setter注入循环依赖的“破局钥匙”。原型Bean无解因为不缓存每次都是新对象无限递归。构造器注入无解对象在诞生前无法提供引用。为什么是三级不是二级为了处理AOP代理的“决策延迟”和引用一致性。记住能解决循环依赖是Spring的“宽容”而不是鼓励你写出循环依赖的代码。良好的设计应该避免这种结构性问题。

相关新闻

Go语言字符串底层原理与高效处理实践

Go语言字符串底层原理与高效处理实践

1. Go语言字符串基础解析Go语言中的字符串(string)是一种不可变的字节序列,底层实现是一个指向字节数组的指针和长度信息的结构体。从语法层面看,字符串可以包含任意字节,但按照惯例通常存储UTF-8编码的Unicode文本。字符串的零值是空字符串&…

2026/7/20 13:42:45阅读更多 →
AI编程工具协同:Claude Code与Codex插件集成实战指南

AI编程工具协同:Claude Code与Codex插件集成实战指南

如果你正在使用 Claude Code 进行日常开发,最近一定被一个选择困扰:当需要更深入的代码审查、更复杂的调试任务,或者想把一个棘手的 bug 直接“甩”给 AI 时,是该切换到另一个独立的 Codex 应用,还是继续在 Claude Cod…

2026/7/20 13:42:45阅读更多 →
5分钟掌握国家中小学智慧教育平台电子课本下载:智能工具助您轻松获取PDF教材

5分钟掌握国家中小学智慧教育平台电子课本下载:智能工具助您轻松获取PDF教材

5分钟掌握国家中小学智慧教育平台电子课本下载:智能工具助您轻松获取PDF教材 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具,帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载,让您更方便地获取…

2026/7/20 13:42:45阅读更多 →
Photoshop AI助手:自然语言修图技术解析与应用

Photoshop AI助手:自然语言修图技术解析与应用

1. Photoshop AI助手功能解析:自然语言修图如何改变设计工作流Adobe在2025年大会上为Photoshop引入的AI助手功能,标志着图像处理软件正式进入自然语言交互时代。这个被官方称为"AI Companion"的功能模块,本质上是一个深度集成在PS工…

2026/7/21 7:37:03阅读更多 →
RaysUp:16%参数实现7倍精度提升的视觉上采样技术

RaysUp:16%参数实现7倍精度提升的视觉上采样技术

1. 项目概述:RaysUp如何用16%参数实现7倍精度提升在计算机视觉领域,我们一直面临着一个根本性矛盾:AI模型理解图像语义的能力越来越强,但输出的空间分辨率却往往低得可怜。这就像让一个高度近视的天才画家作画——他能精准把握画面…

2026/7/21 7:37:03阅读更多 →
AI企业估值方法论:从技术壁垒到商业价值的全面解析

AI企业估值方法论:从技术壁垒到商业价值的全面解析

1. 项目概述:智谱的商业价值评估"7亿收入、万亿市值"这个标题直接指向了一个核心商业命题:如何评估一家技术型企业的真实价值。作为长期关注科技与商业交叉领域的从业者,我见过太多估值泡沫与价值低估的案例。智谱作为AI领域的代表…

2026/7/21 7:37:03阅读更多 →
AI降重工具测评:2026年降重降AI率工具横向对比,哪个更实用?

AI降重工具测评:2026年降重降AI率工具横向对比,哪个更实用?

用AI辅助写论文,常遇到查重报告里AI疑似度居高不下的情况。这时,一款得力的降重降AI率工具就能帮你解围。 2026年的学术写作环境中,单靠大模型创作内容已不够稳妥。专门针对AIGC特征优化的降重降AI率工具,才能真正压低检测数值。…

2026/7/21 7:37:03阅读更多 →
C++高性能内存池实现:从原理到300%性能提升的工程实践

C++高性能内存池实现:从原理到300%性能提升的工程实践

1. 项目概述:为什么我们需要自己动手造轮子?在C高性能开发的圈子里,内存管理是个老生常谈却又避不开的核心话题。每次看到项目里new和delete满天飞,或者标准库容器在频繁插入删除时背后那看不见的内存分配与释放,心里总…

2026/7/21 7:37:03阅读更多 →
多数据源切换:@DS 注解底层调用原理深度剖析

多数据源切换:@DS 注解底层调用原理深度剖析

多数据源切换:DS 注解底层调用原理深度剖析 一、概述 DS 注解来自 dynamic-datasource-spring-boot-starter 组件(苞米豆出品),并非 MyBatis-Plus 核心包,而是其生态扩展。该注解用于在多数据源场景下,声明…

2026/7/21 7:35:03阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/21 0:51:49阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/21 0:51:49阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/21 0:51:49阅读更多 →
Windows+macOS 通用 OpenClaw 部署流程,内置依赖一键启动智能桌面助手

Windows+macOS 通用 OpenClaw 部署流程,内置依赖一键启动智能桌面助手

📌教程适配:OpenClaw v2.7.9 | 兼容 Windows10/11、macOS 双系统 📖前言 当下各类本地 AI 工具层出不穷,多数产品仅能完成文字问答交互,很难直接操控电脑执行实际操作。OpenClaw,业内常称小龙虾 AI&#…

2026/7/21 0:01:46阅读更多 →
Codex 接入后 Bug 反增?复盘从个人演示到团队协作的“流程陷阱”

Codex 接入后 Bug 反增?复盘从个人演示到团队协作的“流程陷阱”

聊《一次Codex项目复盘,问题最后出在流程而不是模型》之前,先说一句实在的:别急着背概念,先看它在真实项目里到底解决什么问题。摘要先把这篇文章的目标说清楚:看完之后,你应该能判断这件事值不值得做&…

2026/7/21 0:01:46阅读更多 →
手把手搓一个五子棋游戏,零代码也能当“游戏开发者”

手把手搓一个五子棋游戏,零代码也能当“游戏开发者”

大家好,还是我。前几期带大家做了心情日记本和可视化大屏,后台有朋友留言:“能不能教点好玩的?我想做游戏,但一行代码都不会。”行,这期就安排。今天的目标:从零做一个五子棋游戏。 带AI对战、三…

2026/7/21 0:03:46阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/20 22:51:39阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/20 18:51:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/20 18:51:18阅读更多 →