Spring中的循环依赖解决方案

前言:测试环境突发BeanCurrentlyInCreationException,导致后端服务启动失败,一看就是Spring的Bean管理中循环依赖。项目中存在Bean的循环依赖,是代码质量低下的表现。多数人寄希望于框架层来给擦屁股,造成了整个代码的设计越来越糟,最后用一些奇技淫巧来填补犯下的错误。

1、什么是循环依赖?

循环依赖指的是两个或者多个bean之间相互依赖,形成一个闭环。直接表现为两个service层互相调用对方

一般场景是一个Bean A依赖Bean B,而Bean B也依赖Bean A.
Bean A → Bean B → Bean A

当然我们也可以添加更多的依赖层次,比如:
Bean A → Bean B → Bean C → Bean D → Bean E → Bean A


2、Spring中的循环依赖

当Spring上下文在加载所有的bean时,他会尝试按照他们他们关联关系的顺序进行创建。比如,如果不存在循环依赖时,例如:
Bean A → Bean B → Bean C
Spring会先创建Bean C,再创建Bean B(并将Bean C注入到Bean B中),最后再创建Bean A(并将Bean B注入到Bean A中)。
但是,如果我们存在循环依赖,Spring上下文不知道应该先创建哪个Bean,因为它们依赖于彼此。在这种情况下,Spring会在加载上下文时,抛出一个BeanCurrentlyInCreationException。

当我们使用构造方法进行注入时,也会遇到这种情况。如果您使用其它类型的注入,你应该不会遇到这个问题。因为它是在需要时才会被注入,而不是上下文加载被要求注入。


3、让我们看一个例子

我们定义两个Bean并且互相依赖(通过构造函数注入)。

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public CircularDependencyA(CircularDependencyB circB) {
        this.circB = circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    @Autowired
    public CircularDependencyB(CircularDependencyA circA) {
        this.circA = circA;
    }
}

现在,我们写一个测试配置类,姑且称之为TestConfig,指定基本包扫描。假设我们的Bean在包“com.baeldung.circulardependency”中定义:

@Configuration
@ComponentScan(basePackages = { "com.baeldung.circulardependency" })
public class TestConfig {
}

最后,我们可以写一个JUnit测试,以检查循环依赖。该测试方法体可以是空的,因为循环依赖将上下文加载期间被检测到。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {
 
    @Test
    public void givenCircularDependency_whenConstructorInjection_thenItFails() {
        // Empty test; we just want the context to load
    }
}

如果您运行这个测试,你会得到以下异常:

BeanCurrentlyInCreationException: Error creating bean with name 'circularDependencyA':
Requested bean is currently in creation: Is there an unresolvable circular reference?

4、解决方法

我们将使用一些最流行的方式来处理这个问题。

4.1 重新设计

当你有一个循环依赖,很可能你有一个设计问题并且各责任没有得到很好的分离。你应该尽量正确地重新设计组件,以便它们的层次是精心设计的,也没有必要循环依赖。

如果不能重新设计组件(可能有很多的原因:遗留代码,已经被测试并不能修改代码,没有足够的时间或资源来完全重新设计......),但有一些变通方法来解决这个问题。

4.2 使用 @Lazy

解决Spring 循环依赖的一个简单方法就是对一个Bean使用延时加载。也就是说:这个Bean并没有完全的初始化完,实际上他注入的是一个代理,只有当他首次被使用的时候才会被完全的初始化。
我们对CircularDependencyA 进行修改,结果如下:

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
        this.circB = circB;
    }
}

如果你现在运行测试,你会发现之前的错误不存在了。

4.3 使用 Setter/Field 注入

其中最流行的解决方法,就是Spring文档中建议,使用setter注入。
简单地说,你对你须要注入的bean是使用setter注入(或字段注入),而不是构造函数注入。通过这种方式创建Bean,实际上它此时的依赖并没有被注入,只有在你须要的时候他才会被注入进来。

让我们开始动手干吧。我们将在CircularDependencyB 中添加另一个属性,并将我们两个Class Bean从构造方法注入改为setter方法注入:

@Component
public class CircularDependencyA {
 
    private CircularDependencyB circB;
 
    @Autowired
    public void setCircB(CircularDependencyB circB) {
        this.circB = circB;
    }
 
    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    private String message = "Hi!";
 
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
 
    public String getMessage() {
        return message;
    }
}

现在,我们对修改后的代码进单元测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class })
public class CircularDependencyTest {
 
    @Autowired
    ApplicationContext context;
 
    @Bean
    public CircularDependencyA getCircularDependencyA() {
        return new CircularDependencyA();
    }
 
    @Bean
    public CircularDependencyB getCircularDependencyB() {
        return new CircularDependencyB();
    }
 
    @Test
    public void givenCircularDependency_whenSetterInjection_thenItWorks() {
        CircularDependencyA circA = context.getBean(CircularDependencyA.class);
 
        Assert.assertEquals("Hi!", circA.getCircB().getMessage());
    }
}

下面对上面看到的注解进行说明:
@Bean:在Spring框架中,标志着他被创建一个Bean并交给Spring管理
@Test:测试将得到从Spring上下文中获取CircularDependencyA bean并断言CircularDependencyB已被正确注入,并检查该属性的值。

4.4 使用 @PostConstruct

打破循环的另一种方式是,在要注入的属性(该属性是一个bean)上使用 @Autowired ,并使用@PostConstruct 标注在另一个方法,且该方法里设置对其他的依赖。

我们的Bean将修改成下面的代码:

@Component
public class CircularDependencyA {
 
    @Autowired
    private CircularDependencyB circB;
 
    @PostConstruct
    public void init() {
        circB.setCircA(this);
    }
 
    public CircularDependencyB getCircB() {
        return circB;
    }
}
@Component
public class CircularDependencyB {
 
    private CircularDependencyA circA;
     
    private String message = "Hi!";
 
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
     
    public String getMessage() {
        return message;
    }
}

现在我们运行我们修改后的代码,发现并没有抛出异常,并且依赖正确注入进来。

4.5 实现ApplicationContextAware and InitializingBean接口

如果一个Bean实现了ApplicationContextAware,该Bean可以访问Spring上下文,并可以从那里获取到其他的bean。实现InitializingBean接口,表明这个bean在所有的属性设置完后做一些后置处理操作(调用的顺序为init-method后调用);在这种情况下,我们需要手动设置依赖。

@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
 
    private CircularDependencyB circB;
 
    private ApplicationContext context;
 
    public CircularDependencyB getCircB() {
        return circB;
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        circB = context.getBean(CircularDependencyB.class);
    }
 
    @Override
    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
        context = ctx;
    }
}
public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    private String message = "Hi!";
 
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
 
    public String getMessage() {
        return message;
    }
}

同样,我们可以运行之前的测试,看看有没有异常抛出,程序结果是否是我们所期望的那样。


5、总结

有很多种方法来应对Spring的循环依赖。但考虑的第一件事就是重新设计你的bean,所以没有必要循环依赖:他们通常是可以提高设计的一种症状。 但是,如果你在你的项目中确实是需要有循环依赖,那么你可以遵循一些这里提出的解决方法。


参考链接:

痛快!SpringBoot终于禁掉了循环依赖

解决Spring Boot 2.6及之后版本取消了循环依赖的支持的问题

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

相机内外参实践之点云投影矢量图

目录 概述 涉及到的坐标变换 深度值可视化 3D点云的2D投影实现 实现效果 参考文献 概述 Camer的内外参在多模态融合中主要涉及到坐标系变换,即像素坐标、相机坐标以及其他坐标系。这篇就针对点云到图像的投影与反投影做代码实践,来构建一张具有深度…

MYSQL 慢查询和慢查询日志

在数据库管理中,慢查询是指执行时间较长的 SQL 查询语句。这类查询可能导致系统性能下降,影响用户体验。为了帮助识别和解决这些性能问题,数据库管理系统通常提供了慢查询日志,用于记录执行时间超过一定阈值的查询。本文将深入探讨…

【pytorch深度学习】使用张量表征真实数据

使用张量表征真实数据 本文为书pytorch深度学习实战的一些学习笔记和扩展知识,涉及到的csv文件等在这里不会给出,但是我会尽量脱离这一些文件将书本想要表达的内容给展示出来。 文章目录 使用张量表征真实数据1. 加载图像文件2. 改变布局3. 加载目录下…

[工业自动化-12]:西门子S7-15xxx编程 - PLC从站 - ET200 SP系列详解

目录 一、概述 1.1 概述 二、系统组成 2.1 概述 2.2 与主站的通信接口模块 2.3 总线适配器 2.4 基座单元 2.5 电子模块 2.6 服务器模块 一、概述 1.1 概述 PLC ET200 SP 是西门子(Siemens)公司生产的一款模块化可编程逻辑控制器(PL…

苹果手机安装未上架APP应用测试教程

STEP 2:找到下载的描述文件(如果没有找到,请到 设置 - 通用 - 描述文件 中查看) STEP 3:安装描述文件 STEP 4:输入解锁密码安装描述文件 STEP 5:同意免责声明,安装描述文件 STEP 6…

开发知识点-Ant-Design-Vue

Ant-Design-Vue a-input a-input Vue组件 a-spin 加载中的效果 data字段 mounted钩子函数 Ant Design Vue 组件库 list-type“picture-card” 上传的图片作为卡片展示 name show-upload-list action :beforeUpload“handleBeforeUpload” :headers“customHeaders” :disabl…

springboot调用第三方接口json转换成对象

请求接口是一个比较常见的需求,接口返回一般是一个json类型,需要进行组装成对应的类,例 {"status_code": 200,"message": "success","data": {"cost": 286.6933,"bom_list": […

人工智能基础——Python:Matplotlib与绘图设计

人工智能的学习之路非常漫长,不少人因为学习路线不对或者学习内容不够专业而举步难行。不过别担心,我为大家整理了一份600多G的学习资源,基本上涵盖了人工智能学习的所有内容。点击下方链接,0元进群领取学习资源,让你的学习之路更加顺畅!记得…

使用ResponseSelector实现校园招聘FAQ机器人

本文主要介绍使用ResponseSelector实现校园招聘FAQ机器人,回答面试流程和面试结果查询的FAQ问题。FAQ机器人功能分为业务无关的功能和业务相关的功能2类。 一.data/nlu.yml文件   与普通意图相比,ResponseSelector训练数据中的意图采用group/intent格…

Vue 3 打印解决方案:Vue-Plugin-HiPrint

文章目录 1. Vue-Plugin-HiPrint 简介2. 安装和使用2.1 安装2.2 引入并注册插件2.3 在组件中使用 3. 配置和高级用法4. 示例应用5. 总结 🎉欢迎来到Java学习路线专栏~Vue 3 打印解决方案:Vue-Plugin-HiPrint ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f37…

x3daudio1_7.dll怎么解决?x3daudio1_7.dll丢失的5个详细处理方法

首先,让我们来了解一下X3DAudio1_7.dll丢失的原因。X3DAudio1_7.dll是一个非常重要的动态链接库文件,它负责处理计算机中的音频输出。然而,由于各种原因,例如软件安装错误、病毒感染、系统升级等,我们可能会遇到X3DAud…

超强C语言跨年烟花代码,精美无比,附源码分步解析

现在大家是不是都觉得程序员不懂浪漫?那真的大错特错,今天就让你们看看什么是程序员的浪漫! 我们今天就来写写《烟花》表白程序,不要惊讶,不要激动,学会了快去拿给心中的那个人看!!…

【论文解读】针对生成任务的多模态图学习

一、简要介绍 多模态学习结合了多种数据模式,拓宽了模型可以利用的数据的类型和复杂性:例如,从纯文本到图像映射对。大多数多模态学习算法专注于建模来自两种模式的简单的一对一数据对,如图像-标题对,或音频文本对。然…

玩转ansible之参数调试和文件操作篇

更多IT技术文章,欢迎关注微信公众号“运维之美” 玩转ansible之参数调试和文件操作篇 01 剧本调试和帮助02 使用场景举例 上节我们学习了使用ansible进行软件安装,那么安装完软件后,就需要linux系统和软件配置修改了,对于linux主机…

Java程序设计2023-第八次上机练习

8-1简单文本编辑器 编写简单文本编辑器,该程序可以新建、打开、编辑和保存文本文件。当用户点击New时,新建一个文件,用户可以编辑文件内容,然后点击Save保存文件。用户点击Open时,选择一个已有文件,然后可…

leetcode:206. 反转链表

一、题目 函数原型: struct ListNode* reverseList(struct ListNode* head) 二、思路 要对链表进行反转,可以有两种方法: 1.改变链表中每个结点之间的指针域指向,最后返回尾结点即可。 2.新建一个链表,将原链表中的结点…

gorm之项目实战-使用gen以及定义表间关系

gorm之项目实战 ER图 关系整理 一对一关系: User 和 UserLog: 一个用户对应一个用户日志,通过 User 模型的主键与 UserLog 模型的外键建立一对一关系。 一对多关系: User 和 Teacher: 一个用户可以对应多个老师&…

win10网络和Internet设置

win10网络设置 win10进入网络设置的常用入口有两个 第一个入口 桌面右下角右键网络图标,然后打开“网络和Internt设置” 第二个入口 桌面的“我的网络”快捷方式,或者我的电脑进去后,左侧栏找到“网络” 右键“属性” 可以看到,…

【论文阅读VLDB13】Online, Asynchronous Schema Change in F1

Online, Asynchronous Schema Change in F1 ABSTRACT 在一个globally 分布式数据库,with shared data, stateless servers, and no global membership.进行一个schema演变。证明许多常见的模式更改可能会导致异常和数据库损坏,通过将破坏引起的模式更改…

Git 命令详解

系列文章目录 C高性能优化编程系列 深入理解软件架构设计系列 高级C并发线程编程 C技能系列 期待你的关注哦!!! 现在的一切都是为将来的梦想编织翅膀,让梦想在现实中展翅高飞。 Now everything is for the future of dream we…