深入理解 Spring 三级缓存:解决单例 Bean 循环依赖的利器

news2025/1/11 11:58:46

目录

一、什么是循环依赖?

二、关于传说中的三级缓存

1.基本概念:

2.三级缓存是哪三级? 

3.【举个例子】那三级缓存是怎么解决上述代码例子中的A、B互相依赖呢?

详细过程:(理解用)

简约版:(理解后看这个就行)

三、关于三级缓存的适用范围

适用的循环依赖范围:

不适用的循环依赖范围:

四、那只用二级缓存行不行?

1.只用二级缓存:

2.三级缓存的优点:


一、什么是循环依赖?

     循环依赖发生在两个或多个 Bean 之间相互依赖的情况下。

     例如,假设我们有两个 Bean:AB,其中 A 依赖 B,而 B 又依赖 A,这就形成了循环依赖。如下所示:

//在这种情况下,Spring 在创建 Bean A 时需要注入 B,
//而创建 B 时又需要注入 A,这就导致了一个无法打破的循环。
@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private A a;
}

有一点“死锁”的味道。


二、关于传说中的三级缓存

1.基本概念:

      三级缓存通过在 Bean 的创建过程中提前暴露未完全初始化的 Bean 的引用,来解决单例 Bean 的循环依赖问题。

      大白话:“管你初始化没初始化好,先拉出来遛遛~”,就是把还没准备好的对象,先拉出来用用,在用的过程中该对象也会逐步配置完全。


2.三级缓存是哪三级? 

· 三级缓存singletonFactories:

      存储的是用于创建单例 Bean 实例的工厂对象。

      在 Bean 创建的早期阶段,Spring 会将一个工厂对象放入三级缓存,以便在需要时生成 Bean 的早期引用。这是为了在需要解决循环依赖时,可以通过这个工厂对象获取到 Bean 的引用。创建的 Bean 引用随后可以被放入二级缓存。

·  二级缓存 earlySingletonObjects:

      用于存储早期暴露的单例 Bean。这些 Bean 已经实例化但还未完成依赖注入和初始化过程

       当一个 Bean 在创建过程中需要提前暴露自身(通常是为了解决循环依赖),Spring 会将其放入二级缓存中,以便其他 Bean 可以引用这个未完全初始化的 Bean。

·  一级缓存:singletonObjects

     这是最主要的缓存,用于存储完全初始化好的单例 Bean 对象。

     当一个 Bean 完全初始化完成后,Spring 会将它放入这个一级缓存中。在后续获取该 Bean 时,Spring 直接从这里获取已经初始化好的实例。


3.【举个例子】那三级缓存是怎么解决上述代码例子中的A、B互相依赖呢?

详细过程:(理解用)

a. 创建 A 的实例

- 当 Spring 容器开始创建 A 时,它会先调用 A 的构造函数来实例化 A 对象。

- 这个时候,A 还没有完成依赖注入(即 B 还没有注入到 A 中),只是创建了 A 的一个原始实例

 b. 将 A 的早期引用放入三级缓存

- Spring 识别到可能存在循环依赖问题,因此它会将 A 的一个工厂对象(ObjectFactory<A>)放入三级缓存中 (singletonFactories)。

- 这个工厂对象用于在后续依赖注入过程中,提供 A 的早期引用(即尚未完全初始化的 A 实例)。

c. 创建 B 的实例

- 紧接着,Spring 需要创建 B,于是调用 B 的构造函数来实例化 B 对象。

- 在 B 的实例化过程中,Spring 发现 B 依赖于 A(因为 B 中有 @Autowired 注解的 A)

d. 从三级缓存中获取 A 的早期引用

- Spring 检查三级缓存,发现 A 的工厂对象已存在。它从三级缓存中获取 A 的早期引用,并将这个引用放入二级缓存 (earlySingletonObjects) 中,同时从三级缓存中移除该工厂对象。

- 此时,B 就可以引用 A 的这个早期引用,完成 B 的依赖注入。

e. 完成 B 的创建

- B 完成依赖注入和初始化后,Spring 将 B 放入一级缓存 (singletonObjects) 中,表示 B 已经完全创建并准备好被使用。

 f. 回到 A,完成依赖注

- 现在,B 已经创建完毕,回到 A,继续完成 A 的依赖注入过程。这时 B 已经存在于一级缓存中,可以直接注入到 A 中。

- 最后,A 也完成了初始化,并被放入一级缓存。

简约版:(理解后看这个就行)

1. 创建 A 实例 → A 放入三级缓存 → 开始创建 B

2. 创建 B 实例 → B 发现依赖 A → 从三级缓存获取 A 的早期引用 → A 的早期引用放入二级缓存

3. B 完成创建 → B 放入一级缓存

4. 回到 A → 从一级缓存获取 B → 完成 A 的创建 → A 放入一级缓存


三、关于三级缓存的适用范围

适用的循环依赖范围:

Spring 三级缓存机制主要用于解决单例(Singleton)作用域的 Bean 循环依赖问题。


     细心的读者已经发现了,标题强调的是“单例Bean循环依赖”,且上述解决AB相互依赖的详细步骤中,关于A、B实例的创建的标红部分


不适用的循环依赖范围:

1.原型(Prototype)作用域的循环依赖

      对于原型作用域的 Bean,每次请求都会创建一个新的实例。因此,原型 Bean 没有全局缓存,也不会在创建过程中被缓存起来没有缓存机制,Spring 无法像处理单例 Bean 那样通过三级缓存提前暴露 Bean 的引用来解决循环依赖。

    如果原型 Bean 之间存在循环依赖,Spring 无法使用三级缓存解决这个问题,通常会抛出 BeanCurrentlyInCreationException

2.构造函数注入的循环依赖

     当两个 Bean 通过构造函数相互依赖时,Spring 无法通过三级缓存来解决这种类型的循环依赖,因为即使是生成原始对象,也要通过构造函数

3.AOP 代理引发的复杂循环依赖

     在使用 AOP时,Spring 可能会创建代理对象。如果 AOP 代理涉及的 Bean 存在循环依赖,AOP 代理对象和原始 Bean 之间的关系可能导致复杂的依赖链,这种依赖链可能无法通过三级缓存的简单缓存机制来解决

4.某些非单例作用域的自定义 Bean Scope

     除了单例和原型作用域外,Spring 允许定义自定义的作用域。如果这种自定义作用域的生命周期和依赖处理方式与单例不同,三级缓存机制可能无法适用于这些自定义作用域的 Bean。

5.循环依赖与非自动装配的复杂场景

      某些情况下,Bean 的依赖关系并非通过 Spring 自动装配管理,而是通过手动获取或其他复杂的方式管理。这种情况下,三级缓存机制可能无法适用。


四、那只用二级缓存行不行?

1.只用二级缓存:

      细心的小伙伴就会问了,那上面的第三级缓存是不是可以砍掉,⼀级缓存保存完整的Bean,⼆级缓存保存不完整的Bean。是这样的道理,二级缓存也能满足循环依赖的注入。在Spring3.6之前,Spring是通过使⽤⼀级和⼆级缓存来解决单例Bean的循环依赖问题。

二级局限性:过早暴露Bean

    仅使用二级缓存,在创建一个 Bean 时,需要立即将其尚未完全初始化的实例放入二级缓存中。这可能会导致半成品 Bean 被过早地使用,容易引发问题,尤其是在 Bean 的初始化步骤中可能进行一些关键操作(如依赖检查、代理生成等)。


2.三级缓存的优点:

      从 3.6版本开始,通过引入三级缓存,Spring能够更全⾯地处理包括AOP在内的更复杂的循环依赖情况。 三级缓存中的 存储的是工厂对象  而不是 Bean 的实例,这样在需要时可以动态生成 Bean 的引用。

       这种设计允许 Spring 在真正需要的时候才将早期引用暴露出来,而不是在 Bean 实例创建的最初阶段。这种延迟初始化方式可以避免很多潜在的并发问题和初始化过程中不一致的情况。

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

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

相关文章

【专题】2024年7月人工智能AI行业报告合集汇总PDF分享(附原数据表)

原文链接:https://tecdat.cn/?p37350 随着人工智能技术的飞速发展&#xff0c;AI已经成为当今时代的重要驱动力。本报告将聚焦于人工智能AI行业的最新动态&#xff0c;涵盖客户服务、体验营销、资产管理以及国产AI大模型应用等多个领域。通过深入研究和分析&#xff0c;我们…

【Python学习-UI界面】PyQt5 小部件5-QCheckBox

样式如下: 当将QCheckBox对象添加到父窗口时&#xff0c;文本标签之前会出现一个矩形框。 和QRadioButton一样&#xff0c;它也是一个可选择的按钮。 它通常用于用户被要求选择一个或多个可用选项的场景。 不同于单选按钮&#xff0c;复选框默认情况下不是互斥的。 为了限制…

Golang 与 Java:编程语言比较及如何选择

Golang 与 Java&#xff1a;哪种语言更好&#xff1f;我们的详细比较指南涵盖了语法、性能和流行度方面的主要差异&#xff0c;以帮助您做出决定。 在规划项目时&#xff0c;有许多编程语言可供选择。但一开始就选择正确的语言是成功启动或交付的关键。选择错误的语言&#xff…

用R语言进行数据类型的检查和基础转换

下面内容摘录自《R 语言与数据科学的终极指南》专栏文章的部分内容&#xff0c;每篇文章都在 5000 字以上&#xff0c;质量平均分高达 94 分&#xff0c;看全文请点击下面链接&#xff1a; 4章8节&#xff1a;用R做数据重塑&#xff0c;行列命名和数据类型转换-CSDN博客 欢迎…

servlet的执行顺序

执行的时候Tomcat先初始化 然后调用 server 根据server来回调请求方式下面会追入源码解释 package com.haogu.servlet;import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.…

【C++二分查找】1954. 收集足够苹果的最小花园周长

本文涉及的基础知识点 C二分查找 LeetCode1954. 收集足够苹果的最小花园周长 给你一个用无限二维网格表示的花园&#xff0c;每一个 整数坐标处都有一棵苹果树。整数坐标 (i, j) 处的苹果树有 |i| |j| 个苹果。 你将会买下正中心坐标是 (0, 0) 的一块 正方形土地 &#xff…

Windows平台RTSP|RTMP播放器如何实现实时录像功能

技术背景 RTSP、RTMP直播播放&#xff0c;这里不再赘述&#xff0c;我们可以很轻松的实现毫秒级的延迟体验&#xff0c;这里讲的是如何实现RTSP、RTSP流的实时录像功能。 我们理解的录像&#xff0c;可能觉得&#xff0c;只要有个开始录像、停止录像接口就够了&#xff0c;实…

mysql速起架子

wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-8.0.21-linux-glibc2.12-x86_64.tar.xz 下载mysql tar xvJf mysql-8.0.21-linux-glibc2.12-x86_64.tar.xz 解压 mv mysql-8.0.21-linux-glibc2.12-x86_64 mysql-8.0 改名 去到bin目录 cd bin mkdir data gr…

【48 Pandas+Pyecharts | 2024年巴黎奥运会奖牌数据分析可视化】

PandasPyecharts | 2024年巴黎奥运会奖牌数据分析可视化 文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 处理奖牌数据2.3 统计各参数国家/地区奖牌数据 &#x1f3f3;️‍&#x1f308; 3. Pyecharts数…

《Unity3D网络游戏实战》通用服务器框架

服务端程序的两大核心是处理客户端的消息和存储玩家数据 模块划分 游戏流程 连接阶段&#xff1a;客户端调用Connect连接服务端即为连接阶段。连接后双端即可通信&#xff0c;但服务端还不知道玩家控制的是哪个角色。于是客户端需要发送一条登录协议&#xff0c;协议中包含用户…

【Rust】使用开源项目搭建瓦片地图服务

本文通过获取在线和离线地图数据&#xff0c;使用开源Rust项目搭建瓦片地图服务&#xff0c;并使用DevExpress的MapControl控件使用自建地图服务 获取地图数据 获取地图数据有很多种方式&#xff0c;这里分别用在线和离线地图数据举例说明 在线下载瓦片地图 打开在线瓦片地…

Java开发代码规范文档

一、命令规范 包命名规范 包Package的作用是将功能相似或相关的类或者接口进行分组管理&#xff0c;便于类的定位和查找&#xff0c;同时也可以使用包来避免类名的冲突和访问控制&#xff0c;使代码更容易维护。通常&#xff0c;包命使用小写英文字母进行命名&#xff0c;并使…

Mysql(三)---增删查改(基础)

文章目录 前言1.补充1.修改表名1.2.修改列名1.3.修改列类型1.4.增加新列1.5.删除指定列 2.CRUD3.新增(Create)3.1.单行插入3.2.指定列插入3.3.多行插入 4.数据库的约束4.1.约束的分类4.2.NULL约束4.3.Unique约束4.4.Default 默认值约束4.5.PRIMARY KEY&#xff1a;主键约束4.6.…

文件共享服务NFS(服务名nfs,端口tcp/2049)

目录 前言 配置文件 工作原理 NFS服务器的配置 查看服务器是否安装 查看服务器状态 开启服务 编写配置文件 客户端挂载 前言 NFS&#xff08;Network File System&#xff09;是一种分布式文件系统协议&#xff0c;它允许网络中的不同计算机共享文件和目录&#xff0…

[网鼎杯2018}fakebook靶场思路,MariaDB注入,通关教程前言

靶场链接&#xff1a;BUUCTF在线评测[%E7%BD%91%E9%BC%8E%E6%9D%AF%202018]Fakebook Tips&#xff1a;更多优质【网络安全】文章主页 你的00后专属网安学习搭子-羽~ 教程 首页 sql万能密码 登录失败 注册一个账号试试 当前页面好像也没有什么注入点 目录扫描 访问 得到user.ph…

用R语言进行绝对值运算、平方根运算和三角函数运算

下面内容摘录自《R 语言与数据科学的终极指南》专栏文章的部分内容&#xff0c;每篇文章都在 5000 字以上&#xff0c;质量平均分高达 94 分&#xff0c;看全文请点击下面链接&#xff1a; 3章3节&#xff1a;R的赋值操作与算术运算_r 链式赋值-CSDN博客文章浏览阅读172次。掌…

代码随想录算法训练营43期 | Day 14——226.翻转二叉树、101. 对称二叉树、104.二叉树的最大深度、二叉树最小深度

代码随想录算法训练营 226.翻转二叉树101. 对称二叉树递归法 104.二叉树的最大深度二叉树最小深度 226.翻转二叉树 leetcode链接 思路&#xff1a; 递归三部曲&#xff1a; 确定递归函数的参数和返回值确定终止条件确定单层递归的逻辑 递归法 TreeNode* invertTreeNode(Tree…

谷歌「Her」来啦!发布Gemini Live语音模式,现场演示两次翻车

重磅惊喜&#xff01; 今天&#xff0c;在MadeByGoogle发布会上&#xff0c;谷歌公布了 AI语音助手 Gemini Live&#xff0c;对标的正是GPT-4o&#xff0c;而且是两周前开放测试的语音模式。 Gemini Live 可以进行顺畅的语音交流&#xff0c;听从语音指令&#xff0c;可以随时…

设计模式21-组合模式

设计模式21-组合模式&#xff08;Composite Pattern&#xff09; 写在前面 动机定义与结构定义结构主要类及其关系 C代码推导优缺点应用场景总结补充叶子节点不重载这三个方法叶子节点重载这三个方法结论 写在前面 数据结构模式 常常有一些组件在内部具有特定的数据结构。如何…

StreamUtils 流处理工具

一、工具类展示 提供对集合的过滤、拼接、排序、MAP转化、分组、转为SET集合等方法 /*** stream 流工具类**/ NoArgsConstructor(access AccessLevel.PRIVATE) public class StreamUtils {/*** 将collection过滤** param collection 需要转化的集合* param function 过滤方法…