Spring 如何解决循环依赖?
Spring 解决循环依赖主要通过三级缓存机制来实现。一、什么是循环依赖循环依赖是指两个或多个 Bean 之间相互依赖形成闭环。例如A 依赖 BB 依赖 A或者更复杂的情况A 依赖 BB 依赖 CC 依赖 A二、Spring 的三级缓存机制Spring 使用三个 Map 来管理 Bean 的实例// 一级缓存存放完整的 Bean已实例化、已填充属性、已初始化privatefinalMapString,ObjectsingletonObjectsnewConcurrentHashMap(256);// 二级缓存存放早期暴露的 Bean已实例化、未填充属性privatefinalMapString,ObjectearlySingletonObjectsnewConcurrentHashMap(16);// 三级缓存存放 Bean 工厂用于生成早期 Bean 的代理对象privatefinalMapString,ObjectFactory?singletonFactoriesnewHashMap(16);三、解决循环依赖的流程以 A、B 相互依赖为例1. 创建 A 的实例// 1. 实例化 A调用构造函数AacreateBeanInstance(a);// 2. 将 A 的工厂放入三级缓存addSingletonFactory(a,()-getEarlyBeanReference(a,a));2. 填充 A 的属性发现依赖 B// 3. 尝试获取 BBbgetBean(b);3. 创建 B 的实例// 4. 实例化 BBbcreateBeanInstance(b);// 5. 将 B 的工厂放入三级缓存addSingletonFactory(b,()-getEarlyBeanReference(b,b));4. 填充 B 的属性发现依赖 A// 6. 尝试获取 AAagetBean(a);// 7. 从三级缓存获取 A 的工厂创建早期引用ObjectFactory?factorysingletonFactories.get(a);AearlyAfactory.getObject();// 8. 将早期 A 从三级缓存移到二级缓存earlySingletonObjects.put(a,earlyA);singletonFactories.remove(a);5. 完成 B 的创建// 9. B 初始化完成放入一级缓存addSingleton(b,b);6. 完成 A 的创建// 10. A 获取到 B填充属性完成// 11. A 初始化完成放入一级缓存addSingleton(a,a);// 12. 从二级缓存移除 AearlySingletonObjects.remove(a);四、核心源码分析1. getBean() 方法protectedTTdoGetBean(Stringname,ClassTrequiredType,...){// 1. 尝试从缓存获取ObjectsharedInstancegetSingleton(beanName);if(sharedInstance!null){return(T)getObjectForBeanInstance(sharedInstance,name,beanName);}// 2. 创建 Bean 实例returncreateBean(beanName,mbd,args);}2. getSingleton() 方法关键protectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){// 1. 从一级缓存获取ObjectsingletonObjectthis.singletonObjects.get(beanName);// 2. 一级缓存没有且当前 Bean 正在创建中if(singletonObjectnullisSingletonCurrentlyInCreation(beanName)){// 3. 从二级缓存获取singletonObjectthis.earlySingletonObjects.get(beanName);// 4. 二级缓存没有且允许早期引用if(singletonObjectnullallowEarlyReference){synchronized(this.singletonObjects){// 5. 双重检查singletonObjectthis.singletonObjects.get(beanName);if(singletonObjectnull){singletonObjectthis.earlySingletonObjects.get(beanName);if(singletonObjectnull){// 6. 从三级缓存获取工厂ObjectFactory?singletonFactorythis.singletonFactories.get(beanName);if(singletonFactory!null){// 7. 调用工厂方法获取早期 BeansingletonObjectsingletonFactory.getObject();// 8. 放入二级缓存this.earlySingletonObjects.put(beanName,singletonObject);// 9. 从三级缓存移除this.singletonFactories.remove(beanName);}}}}}}returnsingletonObject;}3. createBean() 方法protectedObjectcreateBean(StringbeanName,RootBeanDefinitionmbd,Object[]args){// 1. 实例化 BeanObjectbeanInstancedoCreateBean(beanName,mbdToUse,args);returnbeanInstance;}protectedObjectdoCreateBean(StringbeanName,RootBeanDefinitionmbd,Object[]args){// 1. 实例化ObjectbeancreateBeanInstance(beanName,mbd,args);// 2. 允许循环依赖将工厂放入三级缓存booleanearlySingletonExposure(mbd.isSingleton()this.allowCircularReferencesisSingletonCurrentlyInCreation(beanName));if(earlySingletonExposure){addSingletonFactory(beanName,()-getEarlyBeanReference(beanName,mbd,bean));}// 3. 填充属性可能触发循环依赖populateBean(beanName,mbd,bean);// 4. 初始化ObjectexposedObjectinitializeBean(beanName,exposedObject,mbd);// 5. 放入一级缓存if(earlySingletonExposure){ObjectearlySingletonReferencegetSingleton(beanName,false);if(earlySingletonReference!null){exposedObjectearlySingletonReference;}}addSingleton(beanName,exposedObject);returnexposedObject;}五、为什么需要三级缓存一级缓存不够吗一级缓存只存放完整的 Bean循环依赖时 Bean 还未创建完成二级缓存不够吗二级缓存可以解决简单循环依赖但当涉及 AOP 代理时需要三级缓存来延迟创建代理对象三级缓存的作用// 三级缓存存放的是工厂可以延迟创建代理对象addSingletonFactory(beanName,()-getEarlyBeanReference(beanName,mbd,bean));protectedObjectgetEarlyBeanReference(StringbeanName,RootBeanDefinitionmbd,Objectbean){ObjectexposedObjectbean;// 如果需要 AOP 代理在这里创建代理对象if(!mbd.isSynthetic()hasInstantiationAwareBeanPostProcessors()){for(BeanPostProcessorbp:getBeanPostProcessors()){if(bpinstanceofSmartInstantiationAwareBeanPostProcessor){SmartInstantiationAwareBeanPostProcessoribp(SmartInstantiationAwareBeanPostProcessor)bp;exposedObjectibp.getEarlyBeanReference(exposedObject,beanName);}}}returnexposedObject;}六、循环依赖的限制1. 只能解决单例 Bean 的循环依赖// 原型 Bean 不支持循环依赖Scope(prototype)// 会抛出异常2. 构造器注入无法解决// 构造器注入会抛出 BeanCurrentlyInCreationExceptionComponentpublicclassA{privatefinalBb;publicA(Bb){// 构造器注入this.bb;}}3. Async 注解的 Bean// Async 会导致循环依赖失败ComponentpublicclassA{Asyncpublicvoidmethod(){}}七、解决方案1. 使用 Lazy 注解构造器注入ComponentpublicclassA{privatefinalBb;publicA(LazyBb){// 延迟加载this.bb;}}2. 改用 Setter 注入ComponentpublicclassA{privateBb;AutowiredpublicvoidsetB(Bb){this.bb;}}3. 使用 PostConstruct 初始化ComponentpublicclassA{AutowiredprivateBb;PostConstructpublicvoidinit(){// 在这里使用 b}}4. 重新设计避免循环依赖使用事件驱动引入中间层使用设计模式如观察者模式八、总结Spring 通过三级缓存机制巧妙地解决了单例 Bean 的循环依赖问题一级缓存存放完整的 Bean二级缓存存放早期暴露的 Bean已实例化但未填充属性三级缓存存放 Bean 工厂用于生成代理对象核心思想是提前暴露未完全初始化的 Bean 引用让其他 Bean 可以引用它从而打破循环依赖的僵局。但要注意这种机制只适用于 Setter 注入的单例 Bean构造器注入和原型 Bean 的循环依赖需要通过其他方式解决。

相关新闻

Java计算机毕设之基于springboot的种植基地农业信息管理系统的设计与实现基于springboot的农企信息管理平台设计与开发(完整前后端代码+说明文档+LW,调试定制等)

Java计算机毕设之基于springboot的种植基地农业信息管理系统的设计与实现基于springboot的农企信息管理平台设计与开发(完整前后端代码+说明文档+LW,调试定制等)

2026/7/7 8:02:58 阅读更多 →
【课程设计/毕业设计】基于SpringBoot+Vue农企信息管理平台设计与开发基于springboot的农企信息管理平台设计与开发【附源码、数据库、万字文档】

【课程设计/毕业设计】基于SpringBoot+Vue农企信息管理平台设计与开发基于springboot的农企信息管理平台设计与开发【附源码、数据库、万字文档】

2026/7/3 20:53:22 阅读更多 →
【SSVEP】基于SSVEP检测频率导向脑活动的VR实验附Matlab代码

【SSVEP】基于SSVEP检测频率导向脑活动的VR实验附Matlab代码

2026/7/6 14:15:07 阅读更多 →

最新新闻

2026 视频主要内容方案选型:怎么选才能合理控制研发项目成本

2026 视频主要内容方案选型:怎么选才能合理控制研发项目成本

先说明白核心判断 针对学生群体课堂复习、论文调研、知识自测的场景,选2026年视频主要内容处理方案控制项目成本,核心逻辑是匹配需求选功能,不为用不到的企业级服务付费:轻度需求用免费额度覆盖,高频学习需求选适配学…

2026/7/7 8:00:07 阅读更多 →
AI 时代的“数字义乌“来了:NaviAI Hub 要做中国数字产品的全球中转站

AI 时代的“数字义乌“来了:NaviAI Hub 要做中国数字产品的全球中转站

四十年前,义乌人用麻袋背着纽扣和拉链走南闯北,把小商品卖到了全中国。二十年前,义乌国际商贸城的商户开始接来自中东、非洲和欧美的订单,义乌的"小商品指数"成了全球贸易的晴雨表。 现在,一个新的"义乌…

2026/7/7 7:58:06 阅读更多 →
PCF8591与PIC18LF46K40的I2C通信与信号处理实战

PCF8591与PIC18LF46K40的I2C通信与信号处理实战

1. PCF8591与PIC18LF46K40硬件协同设计1.1 芯片选型与核心特性解析PCF8591作为飞利浦(现NXP)推出的8位ADC/DAC混合信号转换器,其最大特色在于单芯片集成4通道ADC和1通道DAC。这款芯片采用CMOS工艺制造,工作电压范围2.5V-6V,典型功耗仅0.25mW。…

2026/7/7 7:56:06 阅读更多 →
Playwright全链路追踪:从UI自动化到问题根因定位的工程实践

Playwright全链路追踪:从UI自动化到问题根因定位的工程实践

1. 项目概述:从“盲人摸象”到“上帝视角”的测试革命在UI自动化测试的世界里,我们这些一线工程师最头疼的,往往不是写不出测试用例,而是当用例失败时,那种面对一片红、却不知从何下手的无力感。一个登录测试失败了&am…

2026/7/7 7:54:05 阅读更多 →
Appium自动化测试中Socket Hang Up错误深度解析与解决方案

Appium自动化测试中Socket Hang Up错误深度解析与解决方案

1. 项目概述:Appium自动化测试中的“Socket Hang Up”幽灵 如果你正在用Appium做移动端自动化测试,特别是跑长时任务或者复杂交互脚本时,突然在日志里看到一行刺眼的“Could not proxy command to the remote server. Original error: socke…

2026/7/7 7:54:05 阅读更多 →
如何快速实现图像超分辨率:面向初学者的完整指南

如何快速实现图像超分辨率:面向初学者的完整指南

如何快速实现图像超分辨率:面向初学者的完整指南 【免费下载链接】Real-ESRGAN-ncnn-vulkan NCNN implementation of Real-ESRGAN. Real-ESRGAN aims at developing Practical Algorithms for General Image Restoration. 项目地址: https://gitcode.com/gh_mirro…

2026/7/7 7:54:05 阅读更多 →

日新闻

鸿蒙新特性:图片画廊与轮播导航——构建沉浸式图片浏览体验

鸿蒙新特性:图片画廊与轮播导航——构建沉浸式图片浏览体验

图片浏览是移动应用中最高频的场景之一。从社交应用的照片流到电商平台的商品图集,从旅游应用的景点相册到摄影作品展示——用户对图片浏览的体验要求不断提高:流畅的切换动画、直观的缩略图导航、便捷的收藏操作、自动播放模式。HarmonyOS NEXT ArkUI 虽…

2026/7/7 0:05:16 阅读更多 →
24V DC-DC降压芯片PW2312B/PW2815,SOT23-6到SOP8-EP方案对比

24V DC-DC降压芯片PW2312B/PW2815,SOT23-6到SOP8-EP方案对比

24V稳压芯片完整选型指南 PW8600 PW75XX PW2815 PW2312B LDODC/DC全方案 一、24V稳压方案概述 24V直流电源在工业自动化、门禁系统、电梯控制、汽车电子、LED驱动、监控设备等场景中应用极广,是最常见的中压直流母线电压。要将24V母线稳定降压至下游MCU、传感器…

2026/7/7 0:05:16 阅读更多 →
RAG+知识图谱混合检索与Graph RAG核心对比

RAG+知识图谱混合检索与Graph RAG核心对比

做企业RAG落地的团队,往往容易卡在一容易踩坑的选型难题: 当需求单纯靠向量RAG搞不定、单纯靠知识图谱也搞不定,必须同时依赖「文本语义理解 实体关系推理」时,到底是做「向量图谱混合检索」就够了,还是必须上「Grap…

2026/7/7 0:07:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/6 8:11:50 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/6 8:11:52 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/6 6:52:56 阅读更多 →

月新闻