spring:如何解决循环依赖?

发布于:2024-10-24 编辑:匿名 来源:网络

1。事情是从一位同事提出的问题开始的。

最近,项目组的一位同事遇到了一个问题,向我征求意见,这立即引起了我的兴趣,因为我是第一次遇到这个问题。平时以为自己对spring的循环依赖问题有比较好的理解,但是遇到这个以及后面的问题之后,刷新了自己的理解。

我们先看一下导致问题的代码片段: 代码语言: javascript copy @Servicepublic class TestService1 { @Autowired private TestService2 testService2; @Async public void test1() { }} 代码语言:javascript copy @Servicepublic class TestService2 { @Autowired private TestService1 testService1; public void test2() { }} 这两段代码中定义了两个Service类:TestService1和TestService2。 TestService2 的实例被注入到 TestService1 中,TestService1 的实例被注入到 TestService2 中。

这形成了一个循环。依靠。

不过,这并不是普通的循环依赖,因为TestService1的test1方法中添加了@Async注解。猜猜程序启动后会发生什么?代码语言: javascriptCopy org.springframework.beans.factory.BeanCurrentlyInCreationException:创建名称为“testService1”的bean时出错:名称为“testService1”的Bean已作为循环引用的一部分注入到其原始版本中的其他bean [testService2]中,但是最终被包裹。

这意味着所述其他 bean 不使用该 bean 的最终版本。这通常是过度渴望类型匹配的结果 - 例如,考虑使用“getBeanNamesOfType”并关闭“allowEagerInit”标志。

报告了错误。 。

。原因是循环依赖的发生。

“这不科学,Spring号称可以解决循环依赖问题,为什么还是会出现这种情况?”如果将上面的代码稍微调整一下: 代码语言:javascript copy @Servicepublic class TestService1 { @Autowired private TestService2 testService2; public void test1( ) { }}去掉TestService1的test1方法上的@Async注解。 TestService1和TestService2都需要注入对方的实例,这也构成了循环依赖。

但重启项目发现运行正常。这是为什么呢?带着这两个问题,让我们开始探索Spring循环依赖的旅程。

2.什么是循环依赖?循环依赖:说白了就是一个或多个对象实例之间存在直接或间接的依赖关系。这种依赖关系就构成了循环调用。

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

第三种情况:多个对象之间的间接依赖。前两种情况的直接循环依赖比较直观,也很好。

识别,但第三种间接循环依赖有时不太容易识别,因为业务代码调用层次很深。 3、循环依赖的N种场景。

spring中出现循环依赖的主要场景有:单例setter注入。这种注入方式应该是spring最常用的。

代码如下: 代码语言:javascript copy @Servicepublic class TestService1 { @Autowired private TestService2 testService2; public void test1() { }} 代码语言:javascript copy @Servicepublic class TestService2 { @Autowired private TestService1 testService1; public void test2() { }} 这是一个经典的循环依赖,但是它工作正常,感谢spring的内部机制,我们甚至无法察觉到它有问题,因为spring默默地帮我们解决了。 spring内部有三级缓存: singletonObjects一级缓存,用于保存已经实例化、注入、初始化的bean实例 EarlySingletonObjects二级缓存,用于保存已经实例化的bean实例 singletonFactories 三级缓存,用于保存已经实例化的bean实例bean创建工厂,以便以后的扩展有机会创建代理对象。

下图告诉大家spring是如何解决循环依赖的: 图1 细心的朋友可能会发现,二级缓存在这种场景下并没有什么用处。那么问题来了,为什么要使用二级缓存呢?试想一下,如果出现以下情况,我们该如何处理呢?代码语言: javascript copy @Servicepublic class TestService1 { @Autowired private TestService2 testService2; @Autowired私有TestService3 testService3; public void test1() { }} 代码语言:javascript copy @Servicepublic class TestService2 { @Autowired private TestService1 testService1; public void test2( ) { }}代码语言:javascript copy @Servicepublic class TestService3 { @Autowired private TestService1 testService1; public void test3() { }}TestService1 依赖于 TestService2 和 TestService3,而 TestService2 依赖于 TestService1,TestService3 也依赖于 TestService1。

按照上图的流程,可以将TestService1注入到TestService2中,并从三级缓存中获取TestService1的实例。假设不使用二级缓存,则将TestService1注入TestService3的流程如下:说白了,两次从三级缓存获取的都是ObjectFactory对象,通过它创建的实例对象可能每次都不一样。

这不是问题吗?为了解决这个问题,spring引入了二级缓存。在上图1中,TestService1对象的实例实际上已经被添加到二级缓存中了。

当TestService1注入TestService3时,只需要从二级缓存中获取对象即可。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 看到图3还有一个问题,为什么我们需要将ObjectFactory对象添加到三级缓存中?直接保存实例对象不就可以了吗?答:不能,因为如果要增强添加到三级缓存的实例对象,直接使用实例对象是行不通的。

spring如何处理这种情况?答案就在AbstractAutowireCapableBeanFactory类的doCreateBean方法的这段代码中:它定义了一个匿名内部类,并通过getEarlyBeanReference方法获取代理对象。其实底层是通过AbstractAutoProxyCreator类的getEarlyBeanReference生成代理对象的。

多实例setter注入是一种偶尔会发生的注入方式,尤其是在多线程场景下。具体代码如下: 代码语言: javascript copy @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Servicepublic class TestService1 { @Autowired private TestService2 testService2; public void test1() { }}代码语言:javascript copy @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)@Servicepublic class TestService2 { @Autowired private TestService1 testService1; public void test2() { }}很多人说这样的话,spring容器启动的时候就会报错。

事实上,这是错误的。我很负责任的告诉你,程序可以正常启动。

为什么?其实答案在AbstractApplicationContext类的refresh方法中就已经告诉了。它将调用 finishBeanFactoryInitialization 方法。

该方法的目的是在spring容器启动时提前初始化一些bean。方法内部调用了preInstantiateSingletons方法。

在红色区域可以清楚地看到:只有非抽象、单例和非延迟加载的类可以提前初始化bean。多实例类是SCOPE_PROTOTYPE类型的类,它不是单例,不会提前初始化bean,因此程序可以正常启动。

如何让他提前初始化bean呢?你只需要定义另一个单例类并将TestService1注入其中即可。代码语言: javascript copy @Servicepublic class TestService3 { @Autowired private TestService1 testService1;} 重新启动程序,执行结果: 代码语言: javascript copy 请求的bean当前正在创建中: 是否存在无法解析的循环引用?果然存在循环依赖。

注意:这个循环依赖问题无法解决,因为它不使用缓存,每次都会生成一个新对象。构造函数注入是一种现在很少使用的注入方式,但我们还是需要了解一下。

看一下下面的代码: 代码语言: javascript copy @Servicepublic class TestService1 { public TestService1(TestService2 testService2) { }} 代码语言: javascript copy @Servicepublic class TestService2 { public TestService2(TestService1 testService1) { }} 运行结果: Code语言: javascript 复制 请求的 bean 当前正在创建中: 是否存在无法解析的循环引用?出现循环依赖,为什么?从图中的流程可以看出,构造函数注入未能添加到三级缓存,并且没有使用缓存,因此无法解决循环依赖问题。单例代理对象setter注入其实是一种比较常见的注入方式。

例如,在使用@Async注解的场景下,会通过AOP自动生成代理对象。我同事的情况也是如此。

代码语言:javascript copy @Servicepublic class TestService1 { @Autowired private TestService2 testService2; @Async public void test1() { }} 代码语言:javascript copy @Servicepublic class TestService2 { @Autowired private TestService1 testService1; public void test2() { }} 从前面了解到,程序启动时会报错,出现循环依赖: 代码语言:javascript Copy org.springframework.beans.factory.BeanCurrentlyInCreationException: Error create bean with name “testService1”:名称为“testService1”的 Bean 已作为循环引用的一部分注入其原始版本中的其他 Bean [testService2],但最终已被包装。这意味着所述其他 bean 不使用该 bean 的最终版本。

这通常是过度渴望类型匹配的结果 - 例如,考虑使用“getBeanNamesOfType”并关闭“allowEagerInit”标志。为什么会出现循环依赖呢?答案就在下图中: 说白了,bean初始化完成之后,还有一步要检查:二级缓存和原始对象是否相等。

由于它对前面的流程无关紧要,所以在前面的流程图中省略了,但这里是重点。我们重点说一下:同事的问题恰好是当他来到这段代码时,发现二级缓存和原来的对象不相等,所以抛出了循环依赖异常。

如果此时将TestService1的名称更改为:TestService6,则其他一切保持不变。代码语言:javascript copy @Servicepublicclass TestService6 { @Autowired private TestService2 testService2; @Async public void test1() { }} 重新启动程序,就会神奇地好起来。

什么?这是为什么呢?这从 spring 的 bean 加载顺序开始。默认情况下,spring按照文件的完整路径递归搜索,按路径+文件名排序,第一个先加载。

所以TestService1先于TestService2加载,而更改文件名后,TestService2先于TestService6加载。为什么TestService2先加载TestService6就没有问题?答案如下图所示:这种情况下,testService6中的二级缓存实际上是空的,不需要从原始对象来判断,所以不会抛出循环依赖。

DependsOn 循环依赖还有一个比较特殊的场景。例如,我们需要在实例化Bean A之前实例化Bean B。

这种情况下,我们可以使用@DependsOn注解。代码语言: javascript copy @DependsOn(value = "testService2")@Servicepublic class TestService1 { @Autowired private TestService2 testService2; public void test1() { }} 代码语言:javascript copy @DependsOn(value = "testService1")@Servicepublic class TestService2 { @Autowired private TestService1 testService1; public void test2() { }}程序启动后,执行结果为: 代码语言:javascript 复制 'testService2' 和 'testService1' 之间的循环依赖关系 在这个例子中,如果 TestService1 和 TestService2 都没有问题如果不添加@DependsOn注解,但添加该注解会导致循环依赖问题。

这是为什么呢?答案就在AbstractBeanFactory类的doGetBean方法的这段代码中:它检查dependsOn的实例是否存在循环依赖,如果存在循环依赖则抛出异常。 4、如何解决循环依赖问题?如果项目中存在循环依赖问题,则说明spring默认无法解决循环依赖。

要看项目的打印日志,看是属于哪种循环依赖。目前包括以下几种情况:通过生成代理对象而产生的循环依赖。

对于这种循环依赖问题有很多解决方案。主要包括:使用@Lazy注解、使用@DependsOn注解进行延迟加载、指定加载顺序、修改文件名、更改循环依赖类等。

加载顺序使用@DependsOn生成的循环依赖关系。对于这样的循环依赖问题,需要找到@DependsOn注解循环依赖的地方,强制其没有循环依赖才能解决问题。

多实例循环依赖等循环依赖问题可以通过将bean改为单实例来解决。使用@Lazy注解可以解决构造函数循环依赖等循环依赖问题。

spring:如何解决循环依赖?

站长声明

版权声明:本文内容由互联网用户自发贡献,本站不拥有所有权,不承担相关法律责任。如果发现本站有涉嫌抄袭的内容,欢迎发送邮件 举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。

标签:

相关文章

  • 通知:即日起,禁止携带Note 7登机,且不得作为航空货物托运或承运

    通知:即日起,禁止携带Note 7登机,且不得作为航空货物托运或承运

    中国民航局昨天发布公告,针对三星Galaxy Note 7手机出现的问题手机,为确保航空运输安全,自10月1日起,3月27日起,三星Galaxy Note 7手机不得携带登机,也不得托运。 具体规定为:旅客及船员严禁随身或手提行李中携带三星Galaxy Note 7手机;严禁将三星Galaxy Note 7手机放

    06-17

  • 李佳琦再次受到质疑,“全网最低价”谁说了算

    李佳琦再次受到质疑,“全网最低价”谁说了算

    双11的战线越来越长,促销方式越来越难懂,冲突也越来越早发生。 今年的商战是由京东发起的。 因花西子眉笔价格得罪网友后,李佳琦直播间再次陷入困境。 上次,一个新的货币计量单位“华西币”诞生了。 这一次,“全网最低价”应该被重新理解。 双11的“简单商战”,谁没说实

    06-21

  • 人造肉生产商v2food获3500万美元A轮融资

    人造肉生产商v2food获3500万美元A轮融资

    据投资界11月30日消息,澳大利亚植物性肉类替代品研发制造商v2food获3500万美元A轮融资,由联邦科学与工业研究组织 (Commonwealth Scientific and Industrial Research Organization) CSIRO 创新基金旗下资本部门 Main Sequence Ventures 出资,私人投资公司 Horizo??n Inve

    06-18

  • 广东设立半导体产业基金 规模110亿

    广东设立半导体产业基金 规模110亿

    天眼查App显示,近日,广东省半导体及集成电路产业股权投资基金二期合伙企业(有限合伙)成立,执行事务办公室为广东粤财基金管理有限公司。 公司投资额0.1亿元人民币,经营范围为私募股权基金从事股权投资、投资管理、资产管理等活动。 合伙人信息显示,该基金由广东粤财投资

    06-18

  • 沉浸式推理互动体验平台“戏剧侦探”获数百万天使融资,经纬中国投资

    沉浸式推理互动体验平台“戏剧侦探”获数百万天使融资,经纬中国投资

    据投资界5月15日消息,沉浸式推理互动体验平台“戏剧侦探”近日获得来自经纬中国的投资。 经纬中国天使轮融资100万元。 本轮融资主要用于产品研发、内容生态建设和团队建设。   剧探本质上是一个“内容+社交”的互动平台,构建强大的社交场景,用脚本驱动场景化社交。 目前

    06-18

  • 路透社:半导体集团 ASMI 预计 2022 年下半年营收增加

    路透社:半导体集团 ASMI 预计 2022 年下半年营收增加

    据路透社报道,荷兰半导体供应商 ASM International (ASMI) 周二预测 2022 年下半年营收增加,因为预计上半年供应链问题将持续存在。 ASMI 在一份声明中表示:“根据目前的情况,我们预计下半年的收入将高于上半年。 ”英特尔和应用材料等半导体集团受到供应链挑战的打击,尽

    06-08

  • 西瓜视频已与BBC、Discovery达成内容合作,独家播出纪录片《哈勃三十年》

    西瓜视频已与BBC、Discovery达成内容合作,独家播出纪录片《哈勃三十年》

    纪录片将成为西瓜视频内容布局的另一重点。 4月22日,西瓜视频与BBC Studios宣布达成内容合作。 双方将共同制作两部新纪录片:《哈勃三十年:揭示宇宙奇观》和《灵长王国》。 同时,BBC将为西瓜视频提供数百部纪录片内容。 西瓜视频还携手另一部纪录片《发现巨头》。 西瓜视频

    06-18

  • 看亚洲之星:现代KOL开创了赢得和影响客户的新方式

    看亚洲之星:现代KOL开创了赢得和影响客户的新方式

    2016年,杭言浩回到家乡马来西亚槟城岛,亲眼目睹了年轻人如何依靠互联网追名逐利,财富,甚至能够靠它谋生。 在互联网上,任何人都可以成为明星15分钟——只需要一段成功的TikTok(抖音海外版)视频。 “然而,真正的影响力是明确自己的独特性,并为他人提供宝贵的知识和经

    06-18

  • 中期协:今年第一季度我国期货市场成交额同比增长83.08%

    中期协:今年第一季度我国期货市场成交额同比增长83.08%

    中国中期协会:今年一季度,我国期货市场成交量同比增长83.08%。 今年一季度,我国期货市场累计交易量18.49亿手,累计成交额0.36万亿元,同比分别增长69.86%和83.08%。 %。 其中,钢材、化工品、油脂、能源等期货品种交易量同比快速增长。 数据还显示,今年3月份全国期货市场

    06-18

  • 据美通社报道,生物科技公司Adcentrx Therapeutics于4月29日宣布完成由剑桥资本

    据美通社报道,生物科技公司Adcentrx Therapeutics于4月29日宣布完成由剑桥资本

    投资社区(ID:pedaily)领投的5000万美元A轮融资,重点关注突破性抗体偶联药物(“Adcentrx”) ADC旗下生物科技公司Therapeutics(“Adcentrx”)宣布完成1万美元A轮融资。 本轮融资由康桥资本领投,博裕资本旗下早期投资平台万物资本跟投。 此次融资将加速Adcentrx在ADC领

    06-18

  • 【话题互动获奖名单&精选点评】开源大模型如何帮助创业项目打造竞争力?

    【话题互动获奖名单&精选点评】开源大模型如何帮助创业项目打造竞争力?

    亲爱的参与者,感谢您参与我们的互动话题“开源大模型如何帮助创业项目打造竞争力?”在这次活动中收到的许多热情的回应和观点给我们带来了启发。 再次感谢您的支持和参与,期待在以后的活动中见到您!这些是本主题讨论中精选的评论。 我们深感荣幸并衷心感谢大家的热情分享。

    06-17

  • 全景影像技术研发公司Insta360获数亿元C+轮融资,拟在A股上市

    全景影像技术研发公司Insta360获数亿元C+轮融资,拟在A股上市

    据投资界3月20日消息,全景影像科技研发公司Insta获得数亿元C+轮融资。 本轮投资方包括迈高控股、华金资本、浪美创投等。 本轮资金将用于产品研发、营销和渠道拓展,提升品牌影响力。 此前,Insta曾于今年3月获得迅雷网络、启明创投、IDG投资的数亿元B轮融资,并于同年7月获得

    06-17