Spring的循环依赖
循环依赖:一个对象或多个对象之间存在直接或间接的依赖关系,这种关系构成了一种循环调用。
- 自己依赖自己的直接依赖
- 两个对象之间的直接依赖
- 多个对象之间的间接依赖
循环依赖的N种场景
- 单例的setter注入(能解决)
- 多例的setter注入(不能解决)
- 构造器注入(不能解决)
- 单例的代理对象setter注入(有可能解决)
- DependsOn循环依赖(不能解决)
第一种情况,直接通过@Autowired直接注入的循环依赖Spring利用内部的三级缓存已解决
spring内部有三级缓存:DefaultSingletonBeanRegistry
- singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
- earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例(此时还未填充属性)
- singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象
spring解决循环依赖的方式
bean1=>一级缓存中获取不到,创建实例=>提前暴露,添加到三级缓存=>依赖注入bean2=>一级缓存中获取不到,创建实例=>提前暴露,添加到三级缓存
=>依赖注入bean1=>三级缓存中获取到实例,并添加到二级缓存=>bean2的依赖注入成功,bean2初始化成功,bean2添加到一级缓存
=>bean1的依赖注入成功,bean1初始化成功,bean1添加到一级缓存=>结束
第二种情况,多例的注入,使用@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)将bean声明为多例的类
此时程序能够正常启动,非单例不会被提前初始化(非抽象、单例 并且非懒加载的类才能被提前初始bean)
这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象。
第三种情况,构造器注入
此种情况在创建实例的时候便进行了对应依赖的创建,无法利用到缓存,也无法解决循环依赖
第四种情况,单例的代理对象setter注入,@Async注解的场景,会通过AOP自动生成代理对象
如图一的情形,bean初始化完成之后,后面还有一步去检查:第二级缓存 和 原始对象 是否相等,如不相等便抛出循环依赖异常
默认情况下,spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载,由于加载顺序的不一致,有可能不会出现循环依赖
对于A中注入B的方式为setter方法,B中注入A的方式为构造器的循环依赖:如果A先加载则可以解决循环依赖,B先加载则不能;依据自然排序的加载顺序加载
第五种情况,使用@DependsOn注解会出现循环依赖
AbstractBeanFactory类的doGetBean会检查dependsOn的实例有没有循环依赖,如果有循环依赖则抛异常
解决循环依赖的方法:
- 使用@Lazy注解,延迟加载(构造器循环依赖)
- 使用@DependsOn注解,指定加载先后关系
- 修改文件名称,改变循环依赖的加载顺序(单例的代理对象setter注入的循环依赖)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix