从零开始 Spring Boot 39:循环依赖

news2024/11/15 19:45:34

从零开始 Spring Boot 39:循环依赖

spring boot

图源:简书 (jianshu.com)

什么是循环依赖

我们看一个例子:

@Component
public class Person {
    private Dog pet;

    public Person(Dog pet) {
        this.pet = pet;
    }
}

@Component
public class Dog {
    private Person owner;

    public Dog(Person owner) {
        this.owner = owner;
    }
}

这里定义了两个 Spring Bean:persondog。这两个 bean 都包含对另一个 bean 的依赖,并且这种依赖是通过构造器来完成注入的。

如果实际运行这样的示例,就会报错:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  dog defined in file [D:\workspace\learn_spring_boot\ch39\cycle-dep\target\classes\com\example\cycledep\Dog.class]
↑     ↓
|  person defined in file [D:\workspace\learn_spring_boot\ch39\cycle-dep\target\classes\com\example\cycledep\Person.class]
└─────┘

错误提示告诉我们这两个 bean 之间出现了循环依赖的问题,因此程序无法正常启动。

这是因为 Spring 要创建Person对象,就必须先创建Dog对象以调用Person的构造器,但要创建Dog对象,同样需要先创建一个Person对象来调用Dog的构造器,这样就陷入了“先有鸡还是先有蛋”的问题,无法进行下去。

需要说明的是,这种循环依赖仅会在构造器注入的情况下出现,属性注入或者 Setter 注入都不会导致,因为后两者并不会影响到构造器的调用和对应 bean 实例的创建,它们都是在 bean 实例创建后的合适时间被初始化/调用的。

解决循环依赖

拆分代码

通常出现这种循环依赖说明代码结构有问题,我们可能需要重新设计代码。拆分其中相互依赖的部分,自然就可以解决循环依赖的问题。

延迟初始化

可以在构造器中产生循环引用的依赖注入上使用@Lazy来解决循环引用问题:

@Component
public class Person {
    private Dog pet;

    public Person(@Lazy Dog pet) {
        this.pet = pet;
    }
}

@Component
public class Dog {
    private Person owner;

    public Dog(Person owner) {
        this.owner = owner;
    }
}

此时,在 Spring 创建Person对象的时候,Spring 会使用一个Dog类型的代理,而不是真正创建一个Dog类型,真正的Dog类型会在之后需要的时候才被创建,而那时候Person类型的 bean 早已完成创建和初始化,因此再调用Dog的构造器进行注入时不会产生循环依赖的问题。

属性注入和 Setter 注入

就像之前说的,这种循环依赖仅会在使用构造器注入时出现,因此我们可以使用属性注入或 Setter 注入的方式来解决循环依赖问题:

@Component
public class Person {
    @Setter(onMethod = @__(@Autowired))
    private Dog pet;
}

@Component
public class Dog {
    @Setter(onMethod = @__(@Autowired))
    private Person owner;
}

除此之外我们还需要修改一个配置选项:

spring.main.allow-circular-references=true
  • 在早期的 Spring 版本中,spring.main.allow-circular-references默认为true,因此可以直接通过这种方式规避循环依赖,但后来 Spring 官方认为循环依赖是“代码异味”,所以将该选项默认设置为false
  • 在目前的版本中,无论spring.main.allow-circular-references的值是什么,构造器注入导致的循环依赖都会报错。
  • Lombok 和依赖注入的内容可以参考我的另一篇文章。

当然属性注入也是同样的效果:

@Component
public class Person {
    @Autowired
    private Dog pet;
}

@Component
public class Dog {
    @Autowired
    private Person owner;
}

部分依赖注入

注意,下面的解决方案都不需要将spring.main.allow-circular-references配置设置为true

循环依赖实际上是因为两个互相存在依赖关系的类型都使用依赖注入实现依赖导致的,因此只要我们不完全使用依赖注入(部分使用依赖注入),就可以解决此类问题:

@Component
public class Person {
    @Autowired
    private Dog pet;

    @PostConstruct
    public void init(){
        this.pet.setOwner(this);
    }
}

@Component
public class Dog {
    @Setter
    private Person owner;
}

这里的关键在于,Person通过依赖注入来初始化pet属性,而Dog类中的owner属性并没有借助依赖注入进行初始化,所以这里并不存在循环依赖。但显然我们需要实现Dog类对Person类的依赖关系,这可以通过 bean 的生命周期回调来完成,比如这个示例中的@PostConstruct标记的回调方法,在这个方法中我们通过this.pet.setOwner(this)的方式创建了Dog实例对Person实例的依赖关系。

当然,类似的你可以使用任意方式的 bean 初始化回调,比如:

@Component
public class Person implements InitializingBean {
    // ...
    @Override
    public void afterPropertiesSet() throws Exception {
        this.pet.setOwner(this);
    }
}

效果是完全相同的。

关于更多 bean 生命周期回调的更多说明,可以参考我的这篇文章。

不使用依赖注入

可以更激进一些,完全不使用依赖注入,自然也就不存在循环依赖的问题,比如:

@Component
public class Person implements InitializingBean, ApplicationContextAware {
    private Dog pet;
    private ApplicationContext ctx;

    @Override
    public void afterPropertiesSet() throws Exception {
        this.pet = ctx.getBean(Dog.class);
        this.pet.setOwner(this);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }
}

@Component
public class Dog {
    @Setter
    private Person owner;
}

在不使用依赖注入的情况下,我们就需要用ApplicationContext.getBean获取 bean 实例,因此需要一个ApplicationContext的引用,这里可以通过ApplicationContextAware接口实现。

当然,实际上我们并不需要这么“极端”,只需要不使用依赖注入处理存在循环依赖的属性即可,对于ApplicationContext可以通过依赖注入获取:

@Component
public class Person implements InitializingBean {
    private Dog pet;
    @Autowired
    private ApplicationContext ctx;

    @Override
    public void afterPropertiesSet() throws Exception {
        this.pet = ctx.getBean(Dog.class);
        this.pet.setOwner(this);
    }
}

总结

如果代码中存在依赖注入,在可能的情况下,最好进行重构,因为依赖注入往往说明存在“代码异味”。如果因为成本之类的原因无法重构,可以通过本文说明的几种方式进行处理。

The End,谢谢阅读。

本文的完整示例代码可以从这里获取。

参考资料

  • 痛快!SpringBoot终于禁掉了循环依赖! - 掘金 (juejin.cn)
  • Circular Dependencies in Spring | Baeldung
  • Dependency Injection :: Spring Framework
  • 从零开始 Spring Boot 27:IoC - 红茶的个人站点 (icexmoon.cn)

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《项目实战》 Jenkins 与 CICD、发布脚本

前言 Jenkins是一个开源的、提供友好操作界面的持续集成(CI)工具,起源于Hudson(Hudson是商用的),主要用于持续、自动的构建/测试软件项目、监控外部任务的运行(这个比较抽象,暂且写上,不做解释…

Monocle2拟时基因富集分析

****Monocle2全部往期精彩系列:1、群成员专享:Monocle2更新(就是重新梳理一下)2、一键跑完monocle2?3、ggplot2个性可视化monocle2结果4、ggplot修饰monocle2拟时热图:一众问题全部解决5、Monocle2终极修改…

IDEA 开发必备神级插件

“工欲善其事, 必先利其器” 分享几款 自用好用到起飞的 IDEA插件 插件下载方式 file->settings->plugins->macketplace—>然后搜索 1. Alibaba Java Coding Guidelines 阿里巴巴编码规范检查插件,让你的代码更规范 2. Tabnine 代码自动补全工具…

redis源码之:跳跃表skiplist

老规矩,先来看看大致结构: debug所用demo如下: #include "src/server.h"void testSDS(); void testAlign(); void testZipList(); void testSkipList(); void testQuickList();int main(int argc, char **argv) { // testAli…

flutter DevTools(1)

在VSCODE中调试 第一步: ① 切换到 vscode 的调试和运行模式, ② 配置好 .vscode 中的启动项 launch.json ③ 共有四种模式 [1] debug : 模式编译产物适合纯 Flutter 侧代码的开发、调试 [2] profile : 的用来做性能分析和测试 [3] release : 的用于打包发布 [4]…

DBeaver连接(DM)达梦数据库

DBeaver连接(DM)达梦数据库 文章目录 1.下载驱动2.DBeaver新建驱动3.连接DM数据库4.JDBC 接口 DBeaver是一款功能强大的数据库管理工具,可以连接多种类型的数据库。下面是连接达梦数据库的步骤: 打开DBeaver并点击“连接”按钮。在弹出的“连接”对话框中…

几个直接在TigerISP上查看全志芯片接Sensor分辨率的方法

TigerISP是全志提供的量产工具,在连接TigerISP时需要填写Sensor名称、Sensor分辨率、Sensor帧率及选择ISP通道、Vich、Wdr模式等… 准备工作:打开debugfs 操作:adb shell进入系统后输入以下两个命令: mount -t debugfs mone /s…

如何成功实施一个数据治理项目?实施步骤有哪些?

企业数字化转型以数据为中心,通过数据驱动业务发展、管理协同和运营。因此,数字化转型关键在于数据,数据治理则需先行。从而更好激发数据生产要素潜能,实现业务数据化、数据价值化,助力企业数字化转型。 那么何为数据…

零代码产品新秀敲敲云与明星产品简道云、轻流对比

零代码产品正在成为当今技术领域的热门话题。随着人们对更快、更简单的开发流程的需求不断增加,零代码产品正在作为一种解决方案得到越来越多的关注。 零代码技术源于低代码技术,与低代码不同的是,零代码不需要任何代码编写。这意味着即使是普…

让人头疼的时序数据预测,这个方案居然三步就搞定了?

数字化时代,时序数据预测已经从一种理论研究转变为各行业实际运营中的关键工具,这种预测可以覆盖广泛的业务领域,比如: 利用历史销售数据进行未来销售趋势的预测 根据既往的电力消耗数据预估未来电力需求 基于过去的股市行情预测未…

释放 OpenAI 和 ESP-BOX 的力量:ChatGPT 与乐鑫 SoC 融合指南

当前,我们正见证着一场技术革命,而 OpenAI 正处于这场变革的最前沿。其中最激动人心的创新之一就是ChatGPT,它运用自然语言处理的力量,打造出更加引人入胜、直观的用户体验。而将 OpenAI 的 API 与物联网设备相结合,更…

Spring Cloud Kubernetes配置使用详情

目录 一、 为什么你需要 Spring Cloud Kubernetes? 二、 Starter 三、 用于 Kubernetes 的 DiscoveryClient 四、Kubernetes 原生服务发现(service discovery) 五、Kubernetes PropertySource 的实现 1、使用 ConfigMap PropertySource …

Unreal Engine 5.1 AI行为树基础入门

ai行为树理解起来其实是npc根据自身一些情况进行一些逻辑执行,而这些逻辑是我们使用ai行为树去实现的。 ai行为树需要一个寻路网格体边界体积,在ue引擎中,体积Actor分为多种,寻路网格体边界体积只是其中的一种。 关于其它的体积&a…

leetcode:448. 找到所有数组中消失的数字(python3解法)

难度:简单 给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。 示例 1: 输入:nums [4,3,2,7,8,2,3,1] 输出&…

初识网络之https的加密与解密

目录 一、https协议的概念 二、加密的概念 三、为什么需要加密 四、常见的加密方式 1. 对称加密 2. 非对称加密 五、数据摘要(数据指纹) 六、数据签名 七、加密方案 1. 方案一:只使用对称加密 2. 方案二:只使用非对称加…

院士大咖齐聚蓉城,论道“疑难眼眶病学术论坛”

“全国疑难眼眶病诊疗带教基地”成立,力促“病有所医 ” 大咖云集,这是一场眼科界领军人汇聚一堂,聚焦疑难眼眶病突破性学术成果及前沿技术的高规格论坛; 规模空前,这是一场围绕眼眶病诊疗技术议题深入探讨交流,为我国…

行为型设计模式08-职责链模式

🧑‍💻作者:猫十二懿 ❤️‍🔥账号:CSDN 、掘金 、个人博客 、Github 🎉公众号:猫十二懿 职责链模式 1、职责链模式介绍 责任链模式(Chain of Responsibility Pattern)…

seatunnel入门案例,集群模式

目录 安装部署 解压 环境变量 安装plugin 添加资源jar包 SEATUNNEL 配置文件 env:环境设置 source:数据源设置 sink:数据去向设置 transform: 数据转换设置 运行方式 seatunnel 引擎(zeta) 本地模式 集群模式 安装部署 解压 tar…

Centos7在Nginx中配置SSL证书

我的环境 阿里云轻量应用服务器(Linux, Centos7.9 x64) 一、申请免费SSL证书 一年可以创建20个 下载证书(我是Nginx服务器) 下载到本地后,打开有两个文件: 二、将SSL证书文件上传至服务器 通过WinScp拖…

图像sensor的特性和驱动解析

1、更换OV9712并且做配置更改和测试 1.1、更改配置脚本 修改Hi3518E_SDK_V1.0.3.0\package\mpp\sample\Makefile.param 1.2、测试运行 运行官方SDK sample的测试版本(打包到本地) 运行ORTP传输的测试版本(RTP实时预览) 1.3、更…