当前位置: 首页 > backend >正文

Spring的循环依赖问题

文章目录

  • 一、什么是循环依赖?
  • 二、Spring 是如何解决循环依赖的?
    • 1.三级缓存
    • 2.解决循环依赖的流程
  • 三、三级缓存机制可以解决所有的循环依赖问题吗?
    • 1. 为什么三级缓存在这里无效?
    • 2. 如何解决构造器循环依赖?
  • 四、循环依赖总结


一、什么是循环依赖?

是指两个或多个Bean互相依赖,导致Spring在实例化它们时形成死循环。

@Component
public class A {@Autowiredprivate B b;
}@Component
public class B {@Autowiredprivate A a;
}

当Spring容器尝试创建A的实例时,发现需要B,则要去创建B;而创建B又需要A,最终就会导致依赖注入死循环。


二、Spring 是如何解决循环依赖的?

1.三级缓存

Spring 容器采用了 三级缓存 来解决单例 Bean 的循环依赖问题。

缓存级别作用
singletonObjects一级缓存,存放完全初始化完成的单例 Bean
earlySingletonObjects二级缓存,存放实例化但未初始化完成的早期 Bean
singletonFactories三级缓存,存放可以创建早期 Bean 的 ObjectFactory

2.解决循环依赖的流程

以BeanA中依赖BeanB,BeanB中又依赖BeanA为例。

1. 创建BeanA
Spirng通过构造方法实例化BeanA得到一个原始对象,将BeanA的ObjectFactory放入三级缓存中,其中ObjectFactory封装了BeanA的早期引用,包括AOP代理。
2. 尝试注入BeanB
Spring为BeanA填充属性,发现需要BeanB,Spring会去检查缓存:
① 如果一级缓存中已经有初始化好的BeanB,就直接返回,反之进入下一步。
② 如果二级缓存中已经有BeanB的早期引用,则把它注入给BeanA,反之进入下一步。
③如果三级缓存中也没有BeanB的ObjectFactory,说明BeanB尚未创建,进入实例化流程。
3. 创建BeanB
当发现缓存中不存在BeanB,则会去实例化BeanB,将其ObjectFactory放入三级缓存。
在创建BeanB的过程中,Spring发现依赖BeanA,那么又会重复第二步去缓存里面寻找BeanA。
因为之前三级缓存中已经存放了BeanA的ObjectFactory,那么可以调用ObjectFactory.getObject()方法生成BeanA的早期引用(也可以是代理对象),将其放入二级缓存并删除三级缓存中BeanA的ObjectFactory。
4. 继续BeanB的创建
使用二级缓存中BeanA的早期引用完成BeanB的属性填充和初始化,将BeanB放入一级缓存。
5. 回到BeanA
从一级缓存中获取BeanB注入到BeanA中完成BeanA的初始化,将BeanA放入一级缓存并删除二级缓存中BeanA的早期引用。


三、三级缓存机制可以解决所有的循环依赖问题吗?

答案:不能
虽然三级缓存机制可以解决大多数字段注入setter注入场景下的循环依赖,但对于构造器注入导致的循环依赖,Spring 是无能为力的。
构造器循环依赖示例:

@Component
public class A {private final B b;public A(B b) { this.b = b; }
}@Component
public class B {private final A a;public B(A a) { this.a = a; }
}

由于 A 和 B 都必须在构造时就传入彼此,Spring 根本无法先创建一个“半成品”放到缓存中 —— 因为构造阶段前就要依赖对方,因此会抛出异常。

1. 为什么三级缓存在这里无效?

构造器注入发生在Bean实例化的最开始阶段,此时还没有机会将ObjectFactory放入三级缓存,也就无法提前暴露 “半成品 Bean” 来打破循环。换句话说,Spring甚至连放进三级缓存的机会都没有,自然就无法利用缓存解决依赖问题。

2. 如何解决构造器循环依赖?

由于构造函数是bean生命周期中最先执行的,Spring框架无法解决构造方法的循环依赖问题。可以使用@Lazy懒加载注解,延迟bean的创建直到实际需要时。

@Component
public class A {private final B b;public A(@Lazy B b) { this.b = b; }
}

这样Spring会为B创建一个代理对象,在真正调用到它时才进行实例化,从而规避构造器阶段的死循环问题。


四、循环依赖总结

  • Spring 中的循环依赖问题是 Bean 生命周期管理中一个经典而复杂的问题。对于 字段注入或 setter 注入 的循环依赖,Spring 通过 三级缓存机制(singletonObjects、earlySingletonObjects、singletonFactories)巧妙地解决了大多数场景。
  • 但对于构造器注入造成的循环依赖,由于在实例化阶段就必须提供依赖对象,Spring 根本无法提前将“半成品”放入缓存,因此无法使用三级缓存解决,最终会导致报错。
  • 为了解决构造器循环依赖,可以使用 @Lazy 注解延迟依赖的加载。Spring 会注入一个代理对象,在真正使用时再初始化该依赖,从而绕开构造阶段的死循环问题。
http://www.xdnf.cn/news/3881.html

相关文章:

  • 工业认知智能:从数据分析到知识创造
  • 自由学习记录(58)
  • Android逆向学习(八)Xposed快速上手(上)
  • GitLab CI/CD变量使用完全指南
  • 修复笔记:SkyReels-V2 项目中的 torch.cuda.amp.autocast 警告和错误
  • 2025年- H24-Lc132-94. 二叉树的中序遍历(树)---java版。
  • 施磊老师rpc(四)
  • QT开发工具对比:Qt Creator、Qt Designer、Qt Design Studio
  • Redis 数据类型详解(一):String 类型全解析
  • RabbitMQ 深度解析:从核心组件到复杂应用场景
  • nt!MiSessionAddProcess函数分析和nt!MmSessionSpace全局变量的关系
  • DeepSeek Copilot idea插件推荐
  • 架构思维:使用懒加载架构实现高性能读服务
  • 运算放大器的主要技术指标
  • 【浅尝Java】变量与数据类型(含隐式类型转换、强制类型转换、整型与字符串互相转换等)
  • JWT解析
  • WebRTC 服务器之Janus视频会议插件信令交互
  • docker:制作镜像+上传镜像+拉取镜像
  • 前端 uni-app 初步使用指南
  • javaEE——单例模式
  • FreeRTOS菜鸟入门(十)·消息队列
  • MySQL用户管理
  • 以下是在 Ubuntu 上的几款PDF 阅读器,涵盖轻量级、功能丰富和特色工具:
  • [原创](现代Delphi 12指南):[macOS 64bit App开发]: [1]如何加载动态链接库, 并无缝支持原生底层开发?
  • iview 表单验证问题 Select 已经选择 还是弹验证提示
  • 【Java 并发编程】线程的基本使用(持续更新优化)
  • 【沐风老师】3DMAX按元素UV修改器插件教程
  • Ubuntu环境下使用uWSGI服务器【以flask应用部署为例】
  • 【2025软考高级架构师】——知识脑图总结
  • Spring AI聊天模型API:轻松构建智能聊天交互