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/13 16:35:27

强烈安利9个AI论文软件,专科生轻松搞定毕业论文!

强烈安利9个AI论文软件&#xff0c;专科生轻松搞定毕业论文&#xff01; AI 工具的崛起&#xff0c;让论文写作不再难 对于专科生来说&#xff0c;撰写毕业论文往往是一道难以逾越的门槛。从选题、查找资料到撰写初稿、反复修改&#xff0c;每一个环节都可能让人感到力不从心。…

作者头像 李华
网站建设 2026/3/26 21:19:22

基于51单片机的智能窗帘晾衣架 WIFI传输 防盗报警

目录基于51单片机的智能窗帘晾衣架系统概述核心功能模块硬件设计要点软件逻辑流程扩展应用场景注意事项源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;基于51单片机的智能窗帘晾衣架系统概述 该系统结合51单片机作为主控芯片&#xff…

作者头像 李华
网站建设 2026/4/11 2:04:31

基于51单片机的智能药盒 WIFI传输 药量检测 定时吃药

目录 功能概述硬件设计软件设计应用场景扩展功能 源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 功能概述 基于51单片机的智能药盒整合了WIFI传输、药量检测和定时提醒功能&#xff0c;旨在帮助用户规律服药。系统通过传感器监测药量…

作者头像 李华
网站建设 2026/4/4 11:17:56

如何用tcpdump诊断tcp数据包问题

tcpdump 是网络故障排查和安全分析中最强大、最常用的工具之一。它能捕获流经网络接口的原始数据包&#xff0c;并提供详细的解读。 下面我将从基础使用、输出信息解读和常用命令示例三个方面为你进行解读。 1. tcpdump 基础使用 基本语法 tcpdump [选项] [过滤表达式] 常用…

作者头像 李华
网站建设 2026/4/15 14:41:49

基于51单片机定时分类自动灌溉大棚浇花系统

目录系统概述核心功能硬件组成软件设计扩展应用注意事项源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;系统概述 51单片机定时分类自动灌溉大棚浇花系统是一种基于51系列单片机的智能控制系统&#xff0c;通过预设程序实现定时、分类灌…

作者头像 李华