news 2026/4/20 15:40:36

Spring 如何解决循环依赖?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring 如何解决循环依赖?

Spring 解决循环依赖主要通过三级缓存机制来实现。

一、什么是循环依赖

循环依赖是指两个或多个 Bean 之间相互依赖,形成闭环。例如:

  • A 依赖 B
  • B 依赖 A

或者更复杂的情况:

  • A 依赖 B
  • B 依赖 C
  • C 依赖 A

二、Spring 的三级缓存机制

Spring 使用三个 Map 来管理 Bean 的实例:

// 一级缓存:存放完整的 Bean(已实例化、已填充属性、已初始化)privatefinalMap<String,Object>singletonObjects=newConcurrentHashMap<>(256);// 二级缓存:存放早期暴露的 Bean(已实例化、未填充属性)privatefinalMap<String,Object>earlySingletonObjects=newConcurrentHashMap<>(16);// 三级缓存:存放 Bean 工厂,用于生成早期 Bean 的代理对象privatefinalMap<String,ObjectFactory<?>>singletonFactories=newHashMap<>(16);

三、解决循环依赖的流程

以 A、B 相互依赖为例:

1. 创建 A 的实例

// 1. 实例化 A(调用构造函数)Aa=createBeanInstance("a");// 2. 将 A 的工厂放入三级缓存addSingletonFactory("a",()->getEarlyBeanReference("a",a));

2. 填充 A 的属性(发现依赖 B)

// 3. 尝试获取 BBb=getBean("b");

3. 创建 B 的实例

// 4. 实例化 BBb=createBeanInstance("b");// 5. 将 B 的工厂放入三级缓存addSingletonFactory("b",()->getEarlyBeanReference("b",b));

4. 填充 B 的属性(发现依赖 A)

// 6. 尝试获取 AAa=getBean("a");// 7. 从三级缓存获取 A 的工厂,创建早期引用ObjectFactory<?>factory=singletonFactories.get("a");AearlyA=factory.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() 方法

protected<T>TdoGetBean(Stringname,Class<T>requiredType,...){// 1. 尝试从缓存获取ObjectsharedInstance=getSingleton(beanName);if(sharedInstance!=null){return(T)getObjectForBeanInstance(sharedInstance,name,beanName);}// 2. 创建 Bean 实例returncreateBean(beanName,mbd,args);}

2. getSingleton() 方法(关键)

protectedObjectgetSingleton(StringbeanName,booleanallowEarlyReference){// 1. 从一级缓存获取ObjectsingletonObject=this.singletonObjects.get(beanName);// 2. 一级缓存没有,且当前 Bean 正在创建中if(singletonObject==null&&isSingletonCurrentlyInCreation(beanName)){// 3. 从二级缓存获取singletonObject=this.earlySingletonObjects.get(beanName);// 4. 二级缓存没有,且允许早期引用if(singletonObject==null&&allowEarlyReference){synchronized(this.singletonObjects){// 5. 双重检查singletonObject=this.singletonObjects.get(beanName);if(singletonObject==null){singletonObject=this.earlySingletonObjects.get(beanName);if(singletonObject==null){// 6. 从三级缓存获取工厂ObjectFactory<?>singletonFactory=this.singletonFactories.get(beanName);if(singletonFactory!=null){// 7. 调用工厂方法获取早期 BeansingletonObject=singletonFactory.getObject();// 8. 放入二级缓存this.earlySingletonObjects.put(beanName,singletonObject);// 9. 从三级缓存移除this.singletonFactories.remove(beanName);}}}}}}returnsingletonObject;}

3. createBean() 方法

protectedObjectcreateBean(StringbeanName,RootBeanDefinitionmbd,Object[]args){// 1. 实例化 BeanObjectbeanInstance=doCreateBean(beanName,mbdToUse,args);returnbeanInstance;}protectedObjectdoCreateBean(StringbeanName,RootBeanDefinitionmbd,Object[]args){// 1. 实例化Objectbean=createBeanInstance(beanName,mbd,args);// 2. 允许循环依赖,将工厂放入三级缓存booleanearlySingletonExposure=(mbd.isSingleton()&&this.allowCircularReferences&&isSingletonCurrentlyInCreation(beanName));if(earlySingletonExposure){addSingletonFactory(beanName,()->getEarlyBeanReference(beanName,mbd,bean));}// 3. 填充属性(可能触发循环依赖)populateBean(beanName,mbd,bean);// 4. 初始化ObjectexposedObject=initializeBean(beanName,exposedObject,mbd);// 5. 放入一级缓存if(earlySingletonExposure){ObjectearlySingletonReference=getSingleton(beanName,false);if(earlySingletonReference!=null){exposedObject=earlySingletonReference;}}addSingleton(beanName,exposedObject);returnexposedObject;}

五、为什么需要三级缓存

一级缓存不够吗?

  • 一级缓存只存放完整的 Bean,循环依赖时 Bean 还未创建完成

二级缓存不够吗?

  • 二级缓存可以解决简单循环依赖
  • 但当涉及 AOP 代理时,需要三级缓存来延迟创建代理对象

三级缓存的作用

// 三级缓存存放的是工厂,可以延迟创建代理对象addSingletonFactory(beanName,()->getEarlyBeanReference(beanName,mbd,bean));protectedObjectgetEarlyBeanReference(StringbeanName,RootBeanDefinitionmbd,Objectbean){ObjectexposedObject=bean;// 如果需要 AOP 代理,在这里创建代理对象if(!mbd.isSynthetic()&&hasInstantiationAwareBeanPostProcessors()){for(BeanPostProcessorbp:getBeanPostProcessors()){if(bpinstanceofSmartInstantiationAwareBeanPostProcessor){SmartInstantiationAwareBeanPostProcessoribp=(SmartInstantiationAwareBeanPostProcessor)bp;exposedObject=ibp.getEarlyBeanReference(exposedObject,beanName);}}}returnexposedObject;}

六、循环依赖的限制

1. 只能解决单例 Bean 的循环依赖

// 原型 Bean 不支持循环依赖@Scope("prototype")// 会抛出异常

2. 构造器注入无法解决

// 构造器注入会抛出 BeanCurrentlyInCreationException@ComponentpublicclassA{privatefinalBb;publicA(Bb){// 构造器注入this.b=b;}}

3. @Async 注解的 Bean

// @Async 会导致循环依赖失败@ComponentpublicclassA{@Asyncpublicvoidmethod(){}}

七、解决方案

1. 使用 @Lazy 注解(构造器注入)

@ComponentpublicclassA{privatefinalBb;publicA(@LazyBb){// 延迟加载this.b=b;}}

2. 改用 Setter 注入

@ComponentpublicclassA{privateBb;@AutowiredpublicvoidsetB(Bb){this.b=b;}}

3. 使用 @PostConstruct 初始化

@ComponentpublicclassA{@AutowiredprivateBb;@PostConstructpublicvoidinit(){// 在这里使用 b}}

4. 重新设计,避免循环依赖

  • 使用事件驱动
  • 引入中间层
  • 使用设计模式(如观察者模式)

八、总结

Spring 通过三级缓存机制巧妙地解决了单例 Bean 的循环依赖问题:

  1. 一级缓存:存放完整的 Bean
  2. 二级缓存:存放早期暴露的 Bean(已实例化但未填充属性)
  3. 三级缓存:存放 Bean 工厂,用于生成代理对象

核心思想是:提前暴露未完全初始化的 Bean 引用,让其他 Bean 可以引用它,从而打破循环依赖的僵局。

但要注意,这种机制只适用于 Setter 注入的单例 Bean,构造器注入和原型 Bean 的循环依赖需要通过其他方式解决。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 5:30:47

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

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/18 3:19:07

计算机Java毕设实战-基于springboot的农企商品产品信息管理平台设计与开发【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/18 5:35:50

基于51单片机的智能药盒 定时吃药远程GSM短信 嵌入式开发

目录 51单片机智能药盒概述硬件组成软件设计要点功能实现流程应用场景与扩展 源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 51单片机智能药盒概述 基于51单片机的智能药盒是一种结合定时提醒、远程监控功能的嵌入式系统&#xff0c;…

作者头像 李华
网站建设 2026/4/18 8:36:22

http请求完整的tcpdump抓包解读

这个过程包括 TCP 三次握手、HTTP 请求/响应 和 TCP 四次挥手。 我们使用以下命令来捕获与 httpbin.org 网站进行的一次简单 HTTP 交互&#xff1a; sudo tcpdump -i any -n -s0 host httpbin.org 然后&#xff0c;在另一个终端使用 curl 发送一个请求&#xff1a; curl ht…

作者头像 李华
网站建设 2026/4/18 8:16:40

AI Agent开发实践:关键步骤和最佳实践

AI Agent开发实践:关键步骤和最佳实践 关键词:AI Agent、开发实践、关键步骤、最佳实践、人工智能 摘要:本文围绕AI Agent开发实践展开,深入探讨其关键步骤和最佳实践。首先介绍了AI Agent开发的背景,包括目的、预期读者、文档结构和相关术语。接着阐述了AI Agent的核心概…

作者头像 李华
网站建设 2026/4/19 0:24:00

信奥赛C++提高组csp-s之倍增算法思想及应用案例(3)

信奥赛C提高组csp-s之倍增算法思想及应用案例(3) 题目描述 小 A 的工作不仅繁琐&#xff0c;更有苛刻的规定&#xff0c;要求小 A 每天早上在 6:006:006:00 之前到达公司&#xff0c;否则这个月工资清零。可是小 A 偏偏又有赖床的坏毛病。于是为了保住自己的工资&#xff0c;小…

作者头像 李华