Spring解决循环依赖:三级缓存

news2025/1/7 6:01:44

1.什么是循环依赖

通俗来讲,循环依赖指的是一个实例或多个实例存在相互依赖的关系(类之间循环嵌套引用)。
在这里插入图片描述

2.Spring如何解决循环依赖

首先,先介绍Spring是如何创建Bean的。

(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象

(2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充

(3)initializeBean:初始化,调用spring xml中的init方法。
循环依赖主要发生在第二步填充属性:在创建对象A时需要填充成员变量B,可是在这时候对象B还没有进行创建。

三级缓存

Spring为了解决单例的循环依赖问题,使用了三级缓存,也就是下文中的三个Map对象。一级缓存用于存放初始化完毕的Bean对象,二级缓存用于存放不完整(仅进行实例化,还没有填充属性)Bean对象,三级缓存用于存放对象工厂(用于创建AOP实例)。

/** Cache of singleton objects: bean name --> bean instance */
    //单例对象的cache
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);

    /** Cache of early singleton objects: bean name --> bean instance */
    //提前暴光的单例对象的Cache
    private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

    /** Cache of singleton factories: bean name --> ObjectFactory */
    //单例对象工厂的cache
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

步骤:

1.Spring首先从一级缓存singletonObjects中获取。

2.如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。

3.如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取.

4.如果从三级缓存中获取到就从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。

Spring解决循环依赖的步骤如下图所示,以创建A、B对象为例。
在这里插入图片描述

  1. 实例化A对象,将未初始化的A对象实例加入三级缓存,构建A对象工厂。
  2. 进行A对象的属性填充,发现A对象引用了未创建的B对象,进行B对象的创建。
  3. 实例化B对象,B对象属性填充时,发现B对象引用了A对象,按照三级缓存查询的步骤查到了A对象在第三级缓存中有对象工厂,于是将A对象创建出来并将A对象的三级缓存移除,加入到第二级缓存。这时的A对象没有填值,仅包含一个引用。
  4. 完成B对象的属性填充、初始化过程,B对象加入到一级缓存中。
  5. 返回到A对象的属性填充过程,填充B对象到A中,再接着初始化、将A对象加入一级缓存中,最终完成A、B对象的创建。

AOP的实现就是在三级缓存的对象工厂上返回代理对象。
在这里插入图片描述

3.Spring无法解决哪些情况下的循环依赖?

  1. 使用构造方法注入,因为加入singletonFactories三级缓存的前提是执行了构造器来创建半成品的对象,所以构造器的循环依赖没法解决。因此,Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!
  2. Bean的作用于为prototype(原型),因为对于原型bean,spring容器只有在需要时才会实例化,初始化它。spring容器不缓存prototype类型的bean,使得无法提前暴露出一个创建中的bean。

4.面试问题

1.第三级缓存是没有实际作用的,为什么不使用一级缓存和二级缓存能解决循环依赖?
答:如果只用一级缓存来解决循环依赖,那么一级缓存中会在某个时间段存在不完整的bean,这是不安全的。

使用二级缓存确实可以解决循环依赖,但是这要求每个原始对象创建出来后就立即生成动态代理对象(如果有的AOP代理增强话),然后将这个动态代理对象放入二级缓存,这就打破了Spring对AOP的设计原则,即:在对象初始化完毕后,再去创建代理对象。所以引入三级缓存,并且在三级缓存中存放一个对象的ObjectFactory,目的就是:延迟代理对象的创建,这里延迟到啥时候创建呢,有两种情况:第一种就是确实存在循环依赖,那么没办法,只能在需要的时候就创建出来代理对象然后放到二级缓存中,第二种就是不存在循环依赖,那就应该正常地在初始化的后置处理器中创建。
因此不直接使用一级缓存和二级缓存来解决循环依赖的原因就是:希望在不存在循环依赖的情况下不破坏Spring对AOP的设计原则

所以总结来说,如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。

2.Spring是如何解决的循环依赖?
答:Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么通过这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

简单点说,Spring解决循环依赖的思路就是:当A的bean需要B的bean的时候,提前将A的bean放在缓存中(实际是将A的ObjectFactory放到三级缓存),然后再去创建B的bean,但是B的bean也需要A的bean,那么这个时候就去缓存中拿A的bean,B的bean创建完毕后,再回来继续创建A的bean,最终完成循环依赖的解决。Spring 利用 三级缓存 巧妙地将出现 循环依赖 时的 AOP 操作 提前到了 属性注入 之前(通过第三级缓存实现的),解决了循环依赖问题。

引用:https://blog.csdn.net/cy973071263/article/details/132676795

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

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

相关文章

【LoadRunner】博客笔记项目 性能测试报告

文章目录 前言一、博客笔记项目性能测试介绍二、编写性能测试脚本&#xff08;VUG&#xff09; 2.1 测试脚本编写步骤 2.2 脚本总代码和结果分析三、创建测试场景&#xff08;Controller&#xff09; 3.1 测试场景创建实现步骤四、生成测试报告&#xff08;Anal…

集合相关知识

string final&#xff0c;不能追加&#xff0c;需要重新new一个 stringbuild&#xff0c;内容 可变&#xff0c;可以重新赋能&#xff0c;能够追加&#xff0c;空间不足创造一个更大的&#xff0c;然后复制过去 stringbufferbuild 线程安全 javac编译&#xff0c;字符串加号…

SpringBoot介绍以及第一个SpringBoot程序

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 文章目录 2.第一个SpringBoot程序2.1Spring Boot介绍2.2使用idea创建Spring Boot程序2.2.1 社区版idea2.2.2专业版idea2.2.3创建SpringBoot项目2.2.4项目代码和目录介绍目录介绍pom文件 2.3Web…

Linux 上 TTY 的起源

注&#xff1a;机翻&#xff0c;未校对。 What is a TTY on Linux? (and How to Use the tty Command) What does the tty command do? It prints the name of the terminal you’re using. TTY stands for “teletypewriter.” What’s the story behind the name of the co…

【边缘计算网关教程】6.松下 Mewtocol TCP 协议

前景回顾&#xff1a;【边缘计算网关教程】5.三菱FX3U编程口通讯-CSDN博客 松下 Mewtocol TCP 协议 适配PLC&#xff1a;松下FP0H 松下XHC60ET 1. 硬件连接 Mewtocol TCP协议采用网口通信的方式&#xff0c;因此&#xff0c;只需要保证网关的LAN口和松下PLC的IP在一个网段即…

STM32怎么把VDD与VSS引脚配置为GPIO?

在 STM32 微控制器中&#xff0c;VDD 和 VSS 引脚是供电引脚&#xff0c;分别用于电源和接地。我收集归类了一份嵌入式学习包&#xff0c;对于新手而言简直不要太棒&#xff0c;里面包括了新手各个时期的学习方向编程教学、问题视频讲解、毕设800套和语言类教学&#xff0c;敲个…

使用base64通用文件上传

编写一个上传文件的组件 tuku,点击图片上传后使用FileReader异步读取文件的内容&#xff0c;读取完成后获得文件名和base64码&#xff0c;调用后端uploadApi,传入姓名和base64文件信息&#xff0c;后端存入nginx中&#xff0c;用于访问 tuku.ts组件代码&#xff1a; <templa…

变量筛选—特征包含信息量

在变量筛选中,通过衡量特征所包含信息量大小,决定是否删除特征,常用的指标有单一值占比、缺失值占比和方差值大小。单一值或缺失值占比越高,表示特征包含信息量越少,不同公司设置不同阈值,一般单一值、缺失值占比高于95%,建议删除。方差值越小,代表特征包含信息量越小。…

JMeter进行HTTP接口测试的技术要点

参数化 用户定义的变量 用的时候 ${名字} 用户参数 在参数列表中传递 并且也是${} csv数据文件设置 false 不忽略首行 要首行 从第一行读取 true 忽略首行 从第二行开始 请求时的参数设置&#xff1a; 这里的名称是看其接口需要的请求参数的名称 这里的变量名称就是为csv里面…

C语言程序设计实例2

C语言程序设计2 问题2_1代码2_1结果2_1 问题2_2代码2_2结果2_2 问题2_3代码2_3结果2_3 问题2_1 函数 f u n fun fun的功能是&#xff1a;计算如下公式前 n n n项的和&#xff0c;并作为函数值返回。 S 1 3 2 2 3 5 4 2 5 7 6 2 ⋅ ⋅ ⋅ ( 2 n − 1 ) ( 2 n 1 ) …

Linux操作系统——数据库

数据库 sun solaris gnu 1、分类&#xff1a; 大型 中型 小型 ORACLE MYSQL/MSSQL SQLITE DBII powdb 关系型数据库 2、名词&#xff1a; DB 数据库 select update database DBMS 数据…

Sql语句之增删改查(CRUD)

Sql语句的书写也被称之为CRUD&#xff0c;即C&#xff08;Create增加&#xff09;R&#xff08;Retrieve添加&#xff09;U&#xff08;Update更新&#xff09;D&#xff08;Delete删除&#xff09;四个操作的首字母。 我们先来看增、删、改这三个相对较为简单&#xff0c;语法…

string+迭代器

int main(){ string s0; string s1("hello word"); cout<<s1<<endl; //遍历string,下标[] for(size_t i0;i<s1.size();i) { cout<<s1[i]<<""; } cout<<endl; } 注意&#xff1a;这里size_t不算/0 迭代器 int main() {st…

Open3D 生成多个球形点云

一、概述 使用 Open3D 创建一个三角网格的球体&#xff0c;并从中均匀采样点生成点云&#xff0c;同时可以指定球体的半径和中心位置。生成 5 个不同大小和位置的圆球形点云&#xff0c;并将它们合并成一个点云以进行显示。 二、代码实现 import open3d as o3d import numpy …

Django任务管理

1、用django-admin命令创建一个Django项目 django-admin startproject task_manager 2、进入到项目下用命令创建一个应用 cd task_manager python manage.py startapp tasks 3、进入models.py定义数学模型 第2步得到的只是应用的必要空文件&#xff0c;要开始增加各文件实际…

博客都在使用的主题切换使用vue2实现思路

效果展示 步骤 1-变量定义css主题色 2-html初始化主题样式 3-vuex存储主题变量&#xff0c;点击触发修改根元素html的样式 4-method触发方法 mutation使用commit action使用dispatch 5-App组件引入该css文件&#xff0c;使用即可 6-将其加入本地存储&#xff0c;刷新后保持主…

【Git分支管理】分支合并冲突及其解决

目录 0.合并冲突 1.创建和切换dev1 ​2.dev1 bbb on dev branch ​3.master ccc on dev branch 4.dev1和master合并冲突 5.合并冲突解决 ​6.git log查看合并流程图 先提交再合并 0.合并冲突 在使用git进行合并操作的时候&#xff0c;在合并两个分支的时候就有可能出…

鹧鸪云户用业务管理系统:全流程管理+源码部署

在当今数字化转型的浪潮中&#xff0c;企业对于高效、灵活且定制化的业务管理系统需求日益增长。为满足这一市场需求&#xff0c;鹧鸪云户用业务管理系统应运而生&#xff0c;它以“全流程管理源码部署”为核心优势&#xff0c;为企业提供了一套集成化、可扩展且易于维护的解决…

最新版康泰克完整版- Kontakt v7.10.5 for Win和Mac,支持m芯片和intel,有入库工具

一。世界最受欢迎的采样器的新篇章 Native Instruments Kontakt是采样器领域的标准&#xff0c;您将获得高质量的滤波器&#xff0c;在这里您将找到经典的模拟电路和最现代的滤波器。每一个都可以根据您的口味进行定制&#xff0c;并且由于它&#xff0c;您可以获得前所未有的声…

1.6.丢弃法

丢弃法 动机&#xff1a;一个好的模型需要对输入数据的扰动足够健壮&#xff0c;丢弃法就是在层之间加入噪音。也可以在数据中使用噪音&#xff0c;等价与Tikhonov正则 无偏差的加入噪音 ​ 对于数据 x x x&#xff0c;加入噪音后的 x ′ x x′的期望值是不变的&#xff0c;…