什么是 spring 的循环依赖?

什么是 spring 的循环依赖?


首先,认识一下什么是循环依赖,举个例子:A 对象被 Spring 管理,并且引入的 B 对象,同样的 B 对象也被 Spring 管理,并且也引入的 A 对象。这种相互被引用的情况,就是所谓的循环依赖

@Component
public class A {@Autowiredprivate B b;
}
@Component
public class B {@Autowiredprivate A a;
}

我们都知道,Spring 是将对象给管理起来,这些对象默认还都是单例的,需要的话从 Spring 中直接取出即可。

1. Spring 是如何存储这些 Bean 呢?

Spring 通过 Map 结构将对象给缓存起来,这里的 Map 其实是分为三个:

  1. 一级缓存(singletonObjects):此缓存中的对象是已经完全创建好的,可以直接使用的 Bean;

    Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    
  2. 二级缓存(earlySingletonObjects):此缓存中存储尚未完全初始化但已经创建了对象实例的 Bean(即提前暴露的 Bean 实例)

    Map<String, Object> earlySingletonObjects = new HashMap<>();
    
  3. 三级缓存(singletonFactories):比较特殊,存放的是 ObjectFactory,这是一个工厂,等到从缓存中取时,会执行其中的 getObject() 方法,来得到代理对象

    Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>()
    

    这样设计是为了在有 AOP 的情况下,可以返回代理对象,而且也能满足循环依赖。

2. 循环依赖的创建过程

  1. 当对象 a 被创建时,就会在缓存中进行查询,先从一级缓存中进行查询,如果没有,接着再从二级缓存中进行查询,如果依旧没用,最后从第三级缓存中查询,如果还是没有,就接着执行后续操作;

  2. 由于缓存中没有 a 对象,就要回到主流程,执行 a 对象的创建

    1. 利用反射创建对象
    2. 这时候就要用到三级缓存,将创建出的对象包装成 ObjectFctory 类型,放到三级缓存中
  3. 填充 a 对象的属性(Bean 的引入也在此执行)

    其中要引入 b 对象,同样也要执行与 a 对象创建相同的流程:

    1. 查询缓存

    2. b 对象的创建:利用反射创建 b 对象,并存入三级缓存中

    3. 填充 b 对象的属性

      此时又要引入 a 对象,由于已经将 a 存储到缓存中,因此这里要执行一些额外的操作(后面说)后,将得到的 a 对象填充到 b 对象中

    4. 初始化

    5. 缓存转移(具体步骤后面说

  4. a 对象初始化

  5. 缓存转移

总体流程大致如下:

3. 补充说明

3.1 在 b 对象创建中填充属性时,从缓存中读取 a 对象要经过什么操作?

参考源码,读取关键部分(红框位置):


首先从三级缓存中获取到 a 对象,由于这个缓存里存放的是 ObjectFactory 类型,并不是真正的对象,这里就要执行 getObject() 方法,从而创建出真正要使用的对象,将得到的真正的对象 a 存入二级缓存中,并将三级缓存中的 a 删除。

3.2 缓存转移的步骤是什么?

此时 b 已经完全创建完毕,所以要将缓存里面的对象进行转移,参考源码:


可以分析出具体操作为:

  1. 将 b 的完整对象放到一级缓存中
  2. 将三级缓存中的 b 移除掉
  3. 将二级缓存中的 b 移除掉(该场景下二级缓存中并没有 b 对象)

a 对象的缓存转移也是同理。

4. 扩展

4.1 第三级缓存的作用?

从上面的执行步骤,可以感觉到三级缓存和里面的 ObjectFactory 类型似乎有点多余,有一级、二级缓存也能搞定循环依赖,三级缓存的意义是什么?

其实主要作用就是为了 AOP。

举例:假如对 b 对象使用了 AOP 切面功能,那么 a 对象引入的 b 对象就必须是 b 对象的代理对象,当 Spring 在没有循环依赖的情况下,是先将普通的完整对象创建好之后,再生成对应的代理对象,然而 Spring 并没有办法提前知道这个对象有没有循环依赖,也不能直接将每个对象都创建出代理对象,所以就需要吧对象包装成 ObjectFactory 类型,提前曝光,等从三级缓存中获取到 ObjectFactory 后,就可以通过 getObject() 方法生成代理对象。

4.2 避免循环依赖的最佳实践

尽管 Spring 提供了循环依赖的解决方案,但在实际开发中应尽量避免循环依赖,因为它可能导致代码耦合度过高、可维护性差等问题。以下是一些避免循环依赖的建议:

  1. 重构代码:将公共逻辑提取到第三个类中,打破循环依赖。
  2. 使用接口或事件驱动:通过接口或事件机制解耦组件间的直接依赖。
  3. 谨慎使用构造器注入:对于可能存在循环依赖的场景,优先使用 Setter 注入

4.3 构造器注入与 Setter 注入的区别

  1. Setter 注入:支持循环依赖。因为 Spring 可以通过三级缓存机制提前暴露部分初始化的 Bean 实例
  2. 构造器注入:不支持循环依赖。原因在于构造器注入要求在创建 Bean 时必须提供所有依赖项,而循环依赖会导致死锁(A 等待 B,B 等待 A)。

因此,如果使用构造器注入,循环依赖会导致 BeanCurrentlyInCreationException 异常。

BeanCurrentlyInCreationException 异常:表示在尝试实例化一个 Bean 时,Spring 容器检测到正在创建的 Bean 已经在创建过程中,导致循环依赖。这种异常通常是由于循环依赖问题引起的。

5. 总结

Spring 通过三级缓存机制解决了单例 Bean 之间的循环依赖问题,但对于原型 Bean 或构造器注入的场景,Spring 无法解决循环依赖。开发时应尽量避免循环依赖,保持代码的清晰性和可维护性

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

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

相关文章

thrift软件、.thrif文件和thrift协议是什么关系,有什么用

Thrift软件、.thrift文件和Thrift协议是Apache Thrift框架的三个核心组成部分&#xff0c;它们协同实现跨语言服务的高效开发与通信。以下是三者关系及作用的详细解析&#xff1a; 一、核心组件关系 1. Thrift软件&#xff08;框架&#xff09; • 定位&#xff1a;Apache Th…

STM32旋转编码器驱动详解:方向判断、卡死处理与代码分析 | 零基础入门STM32第四十八步

主题内容教学目的/扩展视频旋转编码器电路原理&#xff0c;跳线设置&#xff0c;结构分析。驱动程序与调用。熟悉电路和驱动程序。 师从洋桃电子&#xff0c;杜洋老师 &#x1f4d1;文章目录 一、旋转编码器原理与驱动结构1.1 旋转编码器工作原理1.2 驱动程序结构 二、方向判断…

elementplus的cascader级联选择器在懒加载且多选时的一些问题分析

1. 背景 在之前做的一个项目中使用到了element的级联选择器&#xff0c;并且是需要懒加载、多选、父子不关联等等&#xff0c;在选的时候当然没问题&#xff0c;但是回显的时候就会回显不出来&#xff0c;相信大部分伙伴都遇到过这个问题。我在以前出过一篇文章写过关于级联选…

【Python运维】用Python自动化AWS资源管理:利用boto3实现高效管理S3桶和EC2实例

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 随着云计算的普及,AWS(Amazon Web Services)已经成为许多企业和开发者首选的云平台。为了提高工作效率,自动化管理AWS资源成为了一个热…

淘宝关键字搜索接口爬虫测试实战指南

在电商数据分析和市场研究中&#xff0c;通过关键字搜索获取淘宝商品信息是一项重要任务。淘宝开放平台提供了 item_search 接口&#xff0c;允许开发者通过关键字搜索商品&#xff0c;并获取商品列表及相关信息。本文将详细介绍如何设计并测试一个基于该接口的爬虫程序&#x…

【Linux实践系列】:用c语言实现一个shell外壳程序

&#x1f525;本文专栏&#xff1a;Linux Linux实践项目 &#x1f338;博主主页&#xff1a;努力努力再努力wz 那么今天我们就要进入Linux的实践环节&#xff0c;那么我们之前学习了进程控制相关的几个知识点&#xff0c;比如进程的终止以及进程的等待和进程的替换&#xff0c;…

⭐算法OJ⭐N-皇后问题 II【回溯剪枝】(C++实现)N-Queens II

⭐算法OJ⭐N-皇后问题【回溯剪枝】&#xff08;C实现&#xff09;N-Queens 问题描述 The n-queens puzzle is the problem of placing n n n queens on an n n n \times n nn chessboard such that no two queens attack each other. Given an integer n, return the num…

03.06 QT

一、使用QSlider设计一个进度条&#xff0c;并让其通过线程自己动起来 程序代码&#xff1a; <1> Widget.h: #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QThread> #include "mythread.h"QT_BEGIN_NAMESPACE namespace Ui {…

Spring WebFlux 中 WebSocket 使用 DataBuffer 的注意事项

以下是修改后的完整文档&#xff0c;包含在多个多线程环境中使用 retain() 和 release() 方法的示例&#xff0c;且确保在 finally 块中调用 release()&#xff1a; 在 Spring WebFlux 中&#xff0c;WebSocketMessage 主要用于表示 WebSocket 的消息载体&#xff0c;其中 getP…

【CSS】Tailwind CSS 与传统 CSS:设计理念与使用场景对比

1. 开发方式 1.1 传统 CSS 手写 CSS&#xff1a;你需要手动编写 CSS 规则&#xff0c;定义类名、ID 或元素选择器&#xff0c;并为每个元素编写样式。 分离式开发&#xff1a;HTML 和 CSS 通常是分离的&#xff0c;HTML 中通过类名或 ID 引用 CSS 文件中的样式。 示例&#…

2025华为OD机试真题E卷 - 螺旋数字矩阵【Java】

题目描述 疫情期间,小明隔离在家,百无聊赖,在纸上写数字玩。他发明了一种写法:给出数字个数 n (0 < n ≤ 999)和行数 m(0 < m ≤ 999),从左上角的 1 开始,按照顺时针螺旋向内写方式,依次写出2,3,…,n,最终形成一个 m 行矩阵。小明对这个矩阵有些要求: 1、…

地下井室可燃气体监测装置:守护地下安全,防患于未“燃”!

在城市的地下&#xff0c;隐藏着无数的燃气管道和井室&#xff0c;它们是城市基础设施建设的重要部分&#xff0c;燃气的使用&#xff0c;给大家的生活提供了极大的便利。在便利生活的背后&#xff0c;也存在潜在的城市安全隐患。 近年来&#xff0c;地下井室可燃气体泄漏事故…

【使用hexo模板创建个人博客网站】

使用hexo模板创建个人博客网站 环境准备node安装hexo安装ssh配置 使用hexo命令搭建个人博客网站hexo命令 部署到github创建仓库修改_config.yml文件 编写博客主题扩展 环境准备 node安装 进入node官网安装node.js 使用node -v检查是否安装成功 安装成功后应该出现如上界面 …

C# OPC DA获取DCS数据(提前配置DCOM)

OPC DA配置操作手册 配置完成后&#xff0c;访问远程ip&#xff0c;就能获取到服务 C#使用Interop.OPCAutomation采集OPC DA数据&#xff0c;支持订阅&#xff08;数据变化&#xff09;、单个读取、单个写入、断线重连

发行思考:全球热销榜的频繁变动

几点杂感&#xff1a; 1、单机游戏销量与在线人数的衰退是剧烈的&#xff0c;有明显的周期性&#xff0c;而在线游戏则稳定很多。 如去年的某明星游戏&#xff0c;最高200多万在线&#xff0c;如今在线人数是48名&#xff0c;3万多。 而近期热门的是MH&#xff0c;在线人数8…

Unity自定义区域UI滑动事件

自定义区域UI滑动事件 介绍制作1.创建一个Image2.创建脚本 总结 介绍 一提到滑动事件联想到有太多的插件了比如EastTouchBundle&#xff0c;今天想单纯通过UI去做一个滑动事件而不是基于Box2d或者Box去做滑动事件。 制作 1.创建一个Image 2.创建脚本 using UnityEngine; us…

taosd 写入与查询场景下压缩解压及加密解密的 CPU 占用分析

在当今大数据时代&#xff0c;时序数据库的应用越来越广泛&#xff0c;尤其是在物联网、工业监控、金融分析等领域。TDengine 作为一款高性能的时序数据库&#xff0c;凭借独特的存储架构和高效的压缩算法&#xff0c;在存储和查询效率上表现出色。然而&#xff0c;随着数据规模…

《UE5_C++多人TPS完整教程》学习笔记34 ——《P35 网络角色(Network Role)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P35 网络角色&#xff08;Network Role&#xff09;》 的学习笔记&#xff0c;该系列教学视频为计算机工程师、程序员、游戏开发者、作家&#xff08;Engineer, Programmer, Game Developer, Author&#xff09; Stephe…

K8s 1.27.1 实战系列(七)Deployment

一、Deployment介绍 Deployment负责创建和更新应用程序的实例,使Pod拥有多副本,自愈,扩缩容等能力。创建Deployment后,Kubernetes Master 将应用程序实例调度到集群中的各个节点上。如果托管实例的节点关闭或被删除,Deployment控制器会将该实例替换为群集中另一个节点上的…

Linux(Centos 7.6)命令详解:vim

1.命令作用 vi/vim 是Linux 系统内置不可或缺的文本编辑命令&#xff0c;vim 是vi 的加强版本&#xff0c;兼容vi 的所有指令&#xff0c;不仅能编辑文本&#xff0c;而且还具有shell 程序编辑的功能&#xff0c;可以不同颜色的字体来辨别语法的正确性。 2.命令语法 usage: …