Spring 能解决所有循环依赖吗?

news2025/3/11 0:29:05

以下内容基于 Spring6.0.4。

看了上篇文章的小伙伴,对于 Spring 解决循环依赖的思路应该有一个大致了解了,今天我们再来看一看,按照上篇文章介绍的思路,有哪些循环依赖 Spring 处理不了。

严格来说,其实也不是解决不了,所有问题都有办法解决,只是还需要额外配置,这个不是本文的主题,松哥后面再整文章和小伙伴们细聊。

1. 基于构造器注入

如果依赖的对象是基于构造器注入的,那么执行的时候就会报错,代码如下:

@Service
public class AService {
    BService bService;

    public AService(BService bService) {
        this.bService = bService;
    }
}
@Service
public class BService {
    AService aService;

    public BService(AService aService) {
        this.aService = aService;
    }
}

运行时报错如下:

原因分析:

上篇文章我们说解决循环依赖的思路是加入缓存,如下图:

我们说先把 AService 原始对象创建出来,存入到缓存池中,然后再处理 AService 中需要注入的外部 Bean 等等,但是,如果 AService 依赖的 BService 是通过构造器注入的,那就会导致在创建 AService 原始对象的时候就需要用到 BService,去创建 BService 时候又需要 AService,这样就陷入到死循环了,对于这样的循环依赖执行时候就会出错。

更进一步,如果我们在 AService 中是通过 @Autowired 来注入 BService 的,那么应该是可以运行的,代码如下:

@Service
public class AService {
    @Autowired
    BService bService;
}
@Service
public class BService {
    AService aService;

    public BService(AService aService) {
        this.aService = aService;
    }
}

上面这段代码,AService 的原始对象就可以顺利创建出来放到缓存池中,BService 创建所需的 AService 也就能从缓存中获取到,所以就可以执行了。

2. prototype 对象

循环依赖双方 scope 都是 prototype 的话,也会循环依赖失败,代码如下:

@Service
@Scope("prototype")
public class AService {
    @Autowired
    BService bService;
}
@Service
@Scope("prototype")
public class BService {
    @Autowired
    AService aService;
}

这种循环依赖运行时也会报错,报错信息如下(跟前面报错信息一样):

原因分析:

scope 为 prototype 意思就是说这个 Bean 每次需要的时候都现场创建,不用缓存里的。那么 AService 需要 BService,所以就去现场创建 BService,结果 BService 又需要 AService,继续现场创建,AService 又需要 BService…,所以最终就陷入到死循环了。

3. @Async

带有 @Async 注解的 Bean 产生循环依赖,代码如下:

@Service
public class AService {
    @Autowired
    BService bService;

    @Async
    public void hello() {

    }
}
@Service
public class BService {
    @Autowired
    AService aService;

}

报错信息如下:

其实大家从这段报错信息中也能看出来个七七八八:在 BService 中注入了 AService 的原始对象,但是 AService 在后续的处理流程中被 AOP 代理了,产生了新的对象,导致 BService 中的 AService 并不是最终的 AService,所以就出错了!

那有小伙伴要问了,上篇文章我们不是说了三级缓存就是为了解决 AOP 问题吗,为什么这里发生了 AOP 却无法解决?

如下两个前置知识大家先理解一下:

第一:

其实大部分的 AOP 循环依赖是没有问题的,这个 @Async 只是一个特例,特别在哪里呢?一般的 AOP 都是由 AbstractAutoProxyCreator 这个后置处理器来处理的,通过这个后置处理器生成代理对象,AbstractAutoProxyCreator 后置处理器是 SmartInstantiationAwareBeanPostProcessor 接口的子类,并且 AbstractAutoProxyCreator 后置处理器重写了 SmartInstantiationAwareBeanPostProcessor 接口的 getEarlyBeanReference 方法;而 @Async 是由 AsyncAnnotationBeanPostProcessor 来生成代理对象的,AsyncAnnotationBeanPostProcessor 也是 SmartInstantiationAwareBeanPostProcessor 的子类,但是却没有重写 getEarlyBeanReference 方法,默认情况下,getEarlyBeanReference 方法就是将传进来的 Bean 原封不动的返回去。

第二:

在 Bean 初始化的时候,Bean 创建完成后,后面会执行两个方法:

  • populateBean:这个方法是用来做属性填充的。
  • initializeBean:这个方法是用来初始化 Bean 的实例,执行工厂回调、init 方法以及各种 BeanPostProcessor。

大家先把这两点搞清楚,然后我来跟大家说上面代码的执行流程。

  1. 首先 AService 初始化,初始化完成之后,存入到三级缓存中。
  2. 执行 populateBean 方法进行 AService 的属性填充,填充时发现需要用到 BService,于是就去初始化 BService。
  3. 初始化 BService 发现需要用到 AService,于是就去缓存池中找,找到之后拿来用,但是!!!这里找到的 AService 不是代理对象,而是原始对象。因为在三级缓存中保存的 AService 的那个 ObjectFactory 工厂,在对 AService 进行提前 AOP 的时候,执行的是 SmartInstantiationAwareBeanPostProcessor 类型的后置处理器 中的 getEarlyBeanReference 方法,如果是普通的 AOP,调用 getEarlyBeanReference 方法最终会触发提前 AOP,但是,这里执行的是 AsyncAnnotationBeanPostProcessor 中的 getEarlyBeanReference 方法,该方法只是返回了原始的 Bean,并未做任何额外处理。
  4. 当 BService 创建完成后,AService 继续初始化,继续执行 initializeBean 方法。
  5. 在 initializeBean 方法中,执行其他的各种后置处理器,包括 AsyncAnnotationBeanPostProcessor,此时调用的是 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法,在该方法中为 AService 生成了代理对象。
  6. 在 initializeBean 方法执行完成之后,AService 会继续去检查最终的 Bean 是不是还是一开始的 Bean,如果不是,就去检查当前 Bean 有没有被其他 Bean 引用过,如果被引用过,就会抛出来异常,也就是上图大家看到的异常信息。

好啦,这就是松哥和大家分享的三种 Spring 默认无法解决的循环依赖,其实也不是无法解决,需要一些额外配置也能解决,当然,这些额外配置并非本文重点,松哥后面再来和大家介绍~

另外最近两篇关于循环依赖的文章都还没有涉及到源码分析,大家先把思路整清楚,后面松哥再出源码分析的文章~

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

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

相关文章

基于Python的汉字字频统计实验

完整资料进入【数字空间】查看——baidu搜索"writebug" 实验内容 针对不同语料统计汉字的字频,并进行比较。 实验要求和目的 给出前 100 个汉字高频字的频率统计结果; 分别给出前 1、20、100、600、2000、3000、6000 汉字的字频总和&…

makefile项目管理-规则和过程

目录 1、makefile基本规则 2、makefile执行过程 3、makefile的运行规则 1、makefile基本规则 (1)命名:makefile 和 Makefile (2)makefile文件:里面是多个命令的集合,使用make命令执行该文件 …

岩土工程安全监测振弦传感器的发展史

岩土工程安全监测振弦传感器的发展史 岩土工程安全监测是岩土工程学科领域的一个重要组成部分。随着科学技术的不断发展,传感器技术的应用也在不断地创新和完善。振弦传感器是一种重要的监测设备,其应用范围广泛,以其高灵敏度、高精度、长期…

Python实现HBA混合蝙蝠智能算法优化BP神经网络回归模型(BP神经网络回归算法)项目实战

说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 蝙蝠算法是2010年杨教授基于群体智能提出的启发式搜索算法,是一种搜索全局最优解的有效方法…

EPSG代号-坐标系对应表

前言 EPSG 用于标识不同的地理空间参考系统,包括坐标系统、地理坐标系、投影坐标系等。这些标识符可用于许多应用程序和地理信息系统软件,以确保数据在不同系统之间的正确转换和处理。现在,EPSG已被Open Geospatial Consortium(OG…

Js: 读取数据并动态生成表格(读取新数据时,应该删除之前已经渲染出来的数据)

前言 使用JS读取数据并动态生成表格,但是发现在读取新一轮的数据时,新数据是在之前已经渲染的数据后面进行追加。因此需要解决的问题是:在读取新数据之前,把之前已经渲染的数据进行清空。 解决 1、首先写出表格的表头和主干 &…

rsync增量原理,及C++实现

1、目标端将目标文件按700字节为大小分块计算强弱校验值(强:md5 弱:adler32,注:弱校验码计算量小速度快,先比对弱校验码,弱校验值一样再比对强校验码),再结合块号组成一…

【代码随想录 | Leetcode | 第五天】链表 | 移除链表元素 | 设计链表

前言 欢迎来到小K的Leetcode|代码随想录|专题化专栏,今天将为大家带来移除链表元素和设计链表的分享✨ 目录 前言203. 移除链表元素707. 设计链表总结 203. 移除链表元素 ✨题目链接点这里 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所…

Nginx 解析漏洞复现

Nginx 解析漏洞复现 一、环境搭建二、漏洞原理三、漏洞复现 一、环境搭建 如下介绍kali搭建的教程 cd ~/vulhub/nginx/nginx_parsing_vulnerability // 进入指定环境 docker-compose up -d // 启动环境docker-compose ps使用这条命令查看当前正在运行的环境 访问http://y…

生成对抗网络与优化算法(第十次组会)

生成对抗网络与优化算法(第十次组会) 生成对抗网络(Generative Adversarial Network)优化算法生成对抗网络(Generative Adversarial Network) 优化算法

126、仿真-基于51单片机16×16点阵滚动显示仿真设计(Proteus仿真+程序+配套资料等)

方案选择 单片机的选择 方案一:STM32系列单片机控制,该型号单片机为LQFP44封装,内部资源足够用于本次设计。STM32F103系列芯片最高工作频率可达72MHZ,在存储器的01等等待周期仿真时可达到1.25Mip/MHZ(Dhrystone2.1)。内部128k字节…

-bash: ./est.sh: /bin/bash^M: 坏的解释器: 没有那个文件或目录

方法一: 方法二: sed -i s/\r$// xxx.sh

完整的电商平台后端API开发总结

对于开发一个Web项目来说,无论是电商还是其他品类的项目,注册与登录模块都是必不可少的;注册登录功能也是我们在日常生活中最长接触的,对于这个业务场景的需求与逻辑大概是没有什么需要详细介绍的,市面上常见的邮箱注册…

数仓学习---7、数据仓库设计、数据仓库环境准备

这是本人的学习过程,看到的同道中人祝福你们心若有所向往,何惧道阻且长; 但愿每一个人都像星星一样安详而从容的,不断沿着既定的目标走完自己的路程; 最后想说一句君子不隐其短,不知则问,不能则…

Stable Diffusion - 高清局部重绘 (Inpaint) 调整脸部和手部细节

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/131775232 在 Stable Diffusion 中,局部重绘(Inpaint)功能是一种可以让你在图像上删除不想要的区域,并用周围的像素自动填…

JAVA中的Socket编程、通信协议、传输协议

JAVA中的Socket编程 一、Socket概述 Socket,建立起客户端和服务器之间的连接,实现数据的传输和交互,它既可以发送请求,也可以接受请求,一个Socket由一个IP地址和一个端口号唯一确定,利用Socket能比较方便的…

Springboot + Vue 上传Word、PDF文档并保留内部格式

因为业务需求&#xff0c;上传Word文件需要编辑&#xff0c;但如何使用Blob方式&#xff0c;在数据库里存文件&#xff0c;就会造成格式消失。所以修改思路&#xff1a;上传文件到服务器本地&#xff0c;保证数据存储的完整性。 前端 <el-upload class"upload-demo&quo…

centos7.8从卸载python2,安装python3

因为目前所有环境都是python2.7.5&#xff0c;但是项目上使用的是python3.7.5&#xff0c;迫切需要使用python3.7.5验证。安装遇到困难&#xff0c;记录一下。 首先卸载python2&#xff0c;如果不想卸载python2的可以跳过 这里卸载python2和其依赖的yum 先卸载python2.7.5 …

基于意外流行的自适应模因算法求解分布式柔性作业车间调度问题——付源代码和论文

实在是太忙了&#xff0c;终于闲下来更新一下CSDN来介绍自己的工作 《Surprisingly Popular-Based Adaptive Memetic Algorithm for Energy-Efficient Distributed Flexible Job Shop Scheduling》发表在IEEE Transactions on Cybernetics上。 原文链接-可下载 Matlab代码 IEEE…

pdf能转成ppt格式吗?这几个方法你试过了吗

作为办公人士&#xff0c;掌握不同文件格式之间的转换技能是必不可少的。每当毕业季来临&#xff0c;我都会收到许多刚刚步入职场的小伙伴们的求助&#xff0c;寻找PDF转PPT的方法。所以&#xff0c;我总结了以下三种方法&#xff0c;希望能帮助大家解决这个一直以来困扰着许多…