news 2026/4/15 21:14:46

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/16 11:05:03

什么是 Spring IOC?

Spring IOC&#xff08;控制反转&#xff09;详解 一、什么是 IOC IOC&#xff08;Inversion of Control&#xff0c;控制反转&#xff09; 是一种设计思想&#xff0c;不是什么技术实现。它指的是将对象的创建、管理和依赖关系的控制权从程序代码本身转移到外部容器&#xf…

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

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

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

作者头像 李华
网站建设 2026/4/16 10:20:58

基于51单片机的水质检测 PH 浊度 温度 蓝牙 报警

目录 基于51单片机的水质检测系统设计硬件组成模块软件设计要点系统调试注意事项扩展功能方向 源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 基于51单片机的水质检测系统设计 系统概述 该系统以51单片机为核心&#xff0c;集成PH值传…

作者头像 李华
网站建设 2026/4/16 13:36:08

基于SpringBoot的乡村政务办公系统毕业设计源码

博主介绍&#xff1a;✌ 专注于Java,python,✌关注✌私信我✌具体的问题&#xff0c;我会尽力帮助你。 一、研究目的 本研究旨在设计并实现一个基于SpringBoot框架的乡村政务办公系统&#xff0c;以提升乡村政务工作的效率与质量。具体研究目的如下&#xff1a; 提高政务办公…

作者头像 李华
网站建设 2026/4/16 10:21:21

DP Animation Maker(动画制作工具)

链接&#xff1a;https://pan.quark.cn/s/adccc0075a4cDP Animation Maker是一款动画制作工具&#xff0c;可以帮助用户创建动画背景&#xff0c;提供了众多动画特效&#xff0c;即使是小白用户也可以很好的制作出动画。软件特色动画视频。网页横幅。视频。数字贺卡。手机背景。…

作者头像 李华