【Spring】三级缓存解决循环依赖问题

news/2025/3/9 22:41:15/文章来源:https://www.cnblogs.com/sxdcgaq8080/p/18635372

参考地址:

Spring循环依赖:https://zhuanlan.zhihu.com/p/700890658

Spring三级缓存解决循环依赖的问题:https://blog.csdn.net/Trong_/article/details/134063622

 

 ==================================================================

1.什么是循环依赖?

1>说白是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用。
2>循环依赖,发生在属性赋值阶段。
3>循环依赖发生在被显式依赖的情况下,即 ①属性注入依赖②构造函数注入依赖③Setter方法注入依赖
注意:
@DependsOn注解的使用,只是指明Bean的初始化顺序,业务上存在先后装载顺序,但并不是显式的依赖关系,因此@DependsOn注解并不造成循环依赖

 

第一种情况:自己依赖自己的直接依赖

第二种情况:两个对象之间的直接依赖

第三种情况:多个对象之间的间接依赖

前面两种情况的直接循环依赖比较直观,非常好识别,但是第三种间接循环依赖的情况有时候因为业务代码调用层级很深,不容易识别出来。

 

 

 

2.Spring支持解决哪些循环依赖

 

2.1 Spring 初始化Bean的底层过程

Spring创建Bean的过程以及Bean的声明周期,详细可见 【面试 Spring】基础普及 https://www.cnblogs.com/sxdcgaq8080/diary/2024/12/17/18612056

 

 我们可知,Spring生命周期分为四个: 实例化 ->属性赋值 ->初始化 ->销毁

 

而Spring容器要完成Bean的加载,也基本按照这个步骤进行:

1.Spring容器启动 ->

2.从XML或Configuration加载Bean的定义 ->

3.实例化Bean ->

4.装配Bean的属性 (即依赖注入环节)->

5.对Bean做BeanPostProcess前置动作(AOP before) ->

6.对Bean做 @PostConstruct 、Initialization接口afterPropertiesSet() 、XML的init-method等初始化方法的执行 ->

7.对Bean做BeanPostProcess后置动作 (AOP after)->

8.完成对Bean的最终初始化动作。

......

 

好,那循环依赖在什么环节发生?

就是发生在依赖注入的环节  即 装配Bean的属性(属性赋值)阶段。

 

2.2 形成循环依赖的成因

构造函数循环依赖

​ 在使用构造函数注入Bean时,如果两个Bean之间相互依赖,就可能会形成构造函数循环依赖,例如:

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

上述代码,A、B的构造函数分别需要创建对方,A依赖B,B依赖A,它们之间形成了一个循环依赖。

当Spring容器启动时,它会尝试先实例化A,但是在实例化A的时候需要先实例化B,而实例化B的时候需要先实例化A,这样就形成了一个循环依赖的死循环,从而导致应用程序无法正常启动。

属性循环依赖

​ 在使用属性注入Bean时,如果两个Bean之间相互依赖,就可能会形成属性循环依赖。例如:

@Component
public class A {   @Autowired    private B b;
}
=============
@Component
public class B {    @Autowired    private A a;
}

类似的,同样Spring在实例化A时会注入B,而注入B时又需要注入A,形成循环依赖

 

2.3 Spring可以解决那些注入的循环依赖

如上面的过程中,可以确认的是1>Spring支持解决的循环依赖,只针对单例(Singleton)的Bean。原型(Prototype 即多例业务域)的Bean出现循环依赖,Spring会直接抛出异常。2>Spring不支持 A\B初始化顺序上,A依赖B需要构造函数注入才能完成实例化的方式。而Spring支持 A\B 循环依赖通过Setter/属性 注入的方式。

 

1>Spring仅支持解决单例的Bean的循环依赖,不支持原型/多例作用域的Bean的循环依赖

①Spring可以通过 三级缓存 设计,解决 单例模式Bean的循环依赖问题
②Spring无法解决多例/原型(Propotype)作用域Bean的循环依赖问题
原因在于:
多例模式的Bean的创建,是区别于 单例Bean的创建过程。三级缓存针对单例Bean解决循环依赖,主要这样做:
1>A实例化阶段,将 A_BeanFactory放入三级缓存
2>A属性注入阶段,发现依赖B,此时B未创建,所以去实例化B
3>B实例化阶段,将B_BeanFactory放入三级缓存
4>B属性注入阶段,三级B删除,放入二级B,发现依赖A,从三级缓存 一级\二级\三级依次找下去,发现三级中的A
5>使用A_BeanFactory对B完成属性注入,同时A也从三级中删掉,放入了二级缓存
6>B完成属性注入后,删除二级B,将完成初始化的B放入一级缓存
7>接着A继续进行属性赋值,从 一级\二级\三级依次找B,从一级中找到B,完成属性注入
8>删除二级A,放入一级A,AB至此都完成了初始化。可以看出,单例的Bean是在  其生命周期 不同阶段,加入三级缓存,才有了可操作的空间。
而多例的Bean,是每次请求容器getBean()都会去创建新的Bean,那这个过程中多例的Bean又依赖了其他多例的Bean,就会导致被依赖的多例的Bean也加入创建过程,这样就形成了 无限递归的 多例创建过程,最终导致栈溢出(StockOverflowError) 或 内存溢出(OutOfMemoryError).

 

2>Spring支持的单例Bean下循环依赖时的注入方式

 

从上面一节,我们可以知道,Spring使用三级缓存解决 单例Bean的依赖注入,是利用了Spring管理Bean的生命周期过程,才有了可操作的空间。1.实例化阶段 只创建Bean
2.属性赋值解决  才解决依赖注入
3.初始化前后 AOP切面增强逻辑因此,以 A和B循环依赖为例,
先实例化A,在实例化B的先后顺序来讲,如果AB均采用构造器注入,就无法给三级缓存操作空间,即第一步A实例化就无法成功。
如果A对B采用构造器注入,B对A采用Setter注入,那同理第一步A实例化就无法成功。因为实例化一个对象,需要通过执行它的构造函数。而其他依赖注入方式,无论属性注入还是 setter注入 都是可以在 通过实例化阶段后的第二阶段再调用,因此给了三级缓存可操作的空间。

 

 

 

3.Spring解决循环依赖的方案--三级缓存

3.1 三级缓存示意图

// 一级缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

 

 

 

3.2 三级缓存解决Spring中单例Bean的循环依赖问题

三级缓存,从一级、二级、三级依次查找的使用顺序。1>实例化A,将A_ObjectFactory放入三级缓存,表示A已经开始了实例化。objectFactory.getObject()内部最终调用getEarlyBeanReference()方法2>实例化完A,开始对A进行属性赋值阶段(即依赖注入),发现A依赖B
3>从三级缓存依次查找,未找到B
4>实例化B,将B_ObjectFactory放入三级缓存,表示B开始了实例化。
5>实例化完B,开始对B进行属性赋值阶段,发现B依赖A
6>从三级缓存依次查找,在三级缓存找到A
7>将A_ObjectFactory从三级缓存取出,判断A是否被AOP切面代理,如果A被代理,则objectFactory.getObject()内部最终调用getEarlyBeanReference()方法,返回A的代理对象A_proxy,放入二级缓存如果A未被代理,则直接返回真实A对象,放入二级缓存虽然此时A还未完全完成属性赋值和初始化,但真实A 或代理对象A_proxy 已经足以完成对B的依赖注入了
8>B完成属性赋值后,完成初始化动作,放入一级缓存,清理二级、三级缓存中的B
9>从二级缓存拿出A或A_proxy,从三级缓存依次查找B,找到B完成对A的属性赋值,完成A的属性赋值和初始化,A放入一级缓存,二级、三级缓存清理
10>完成单例的A和B循环依赖的初始化。

 

 

4. 答疑解惑

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/859945.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uni-app 设置多语言切换uni-i18n插件

安装uni-i18n插件npm install uni-i18nmain.js文件中引入并初始化VueI18n///main.js import messages from ./language/index let i18nConfig = {locale: uni.getLocale(),messages }import Vue from vue import VueI18n from vue-i18n import App from ./App Vue.use(VueI18n)…

查询数据库开始时间和结束时间字段中包括了给定时间区间的数据

表数据示例: 查询区间:2024-12-03 10:00:00 - 2024-12-06 18:00:00 mysql示例:SELECT * FROM time_test WHERE ((start_time > 2024-12-03 10:00:00 AND (2024-12-06 18:00:00 > end_time OR ( 2024-12-06 18:00:00 > start_time AND 2024-12-06 18:00:00 < e…

ASP.NET 自定义控件

创建Web Forms 用户控件 选中项目右键#新建 #Web Forms #Web Forms 用户控件代码示例<div id="footer"><div style="font-family: @宋体; font-size: 11px;"><strong >Martin Emprex Textiles(Zhongshan-China)Limited</strong>&l…

协同办公如何帮助车企抓住以旧换新市场机会

随着国家对以旧换新购车政策的进一步细化和推广,汽车市场的竞争再度升级。对于车企而言,这既是一个重要的市场增长契机,也是一次全方位提升销售运营效率的考验。通过在线协同工具,车企能够更高效地推动内部协作、优化外部销售网络,在换新浪潮中构建敏捷的销售体系。 敏捷销…

智能感知的未来:传感器融合与数字样机技术

2024年是汽车产业蓬勃发展的一年。汽车保有量的迅速攀升固然可喜可贺,然而伴随而来的交通事故频发、道路拥堵、停车困难、环境污染、能源消耗等现实问题日益凸显,严重阻碍了汽车工业的持续健康发展。 据世界卫生组织最新统计,全世界范围内每年由道路交通事故引发的人员死亡人…

SVG前端画图

SVG(Scalable Vector Graphics)是一种基于XML的标记语言,用于描述二维矢量图形,它可以在Web浏览器中进行展示。 SVG优点是:可伸缩、分辨率无损失,不管是电脑还是手机屏幕上都能够清晰地显示,支持互动和动画等特效; 缺点是:不支持复杂的渲染效果,如模糊、阴影和透明度…

uniapp项目打包为桌面应用的方法步骤

1、在控制台安装electron cnpm install electron -g 2、在控制台安装electron-packager cnpm install electron-packager -g 3、uniapp的manifest.json修改image.png运行的基础路径修改为:./ 不然打包出来会出现白屏,读取不到,因为打包出来的h5默认加载地址为/static/ 去掉启…

dropDownButton使用方法

添加DropDownButton 控件添加PopuMenu控件DropDownButton控件绑定PopuMenu控件

智能问答模型升级,通义灵码新增图片多模态问答模式等新能力,项目秒上手

通义灵码智能问答模型升级 Qwen2.5 Coder、输入交互升级并丰富上下文支持,全新支持多模态图片问答模式等。通义灵码智能问答模型升级 Qwen2.5 Coder、输入交互升级并丰富上下文支持,全新支持多模态图片问答模式等。 1. 智能问答模型升级到最新 Qwen2.5 Coder 智能问答升级到最…

Oracle数据库关于日期TO_DATE的用法

1、在Oracle数据库中,常用的日期格式,比如获取当前时间 SELECT SYSDATE AS 当前时间 FROM DUAL; 2、稽核本月第一天的数据至今,日期获取如下 SELECT TRUNC(SYSDATE, MM) AS 当月第一天 FROM DUAL; 3、日期时间格式 select to_date(2021/7/1 23:59:59,yyyy/mm/dd hh24:mi:ss…

window环境下 IIS负载均衡

目录负载均衡分类DNS轮询 CDN IP负载均衡网络七层协议ARR(Application Request Route)配置IIS集群 负载均衡配置 负载监控 Nginx获取真实客户端IP地址ARR 负载均衡 任何的负载均衡技术都要想办法建立某种一对多的映射机制: 一个请求的入口映射到多个处理请求的节点,从而实现…