Spring三级缓存处理循环依赖的过程

news2024/11/23 3:10:43

Spring三级缓存

Spring三级缓存是什么?

image-20231129215130941

  1. 一级缓存:单例池。存放的是完整的Bean对象。经过完整的生命周期。
  2. 二级缓存:存放需要提前暴露的Bean对象。也就不完整的Bean对象。需要提前暴露就是指,可能会被循环依赖。(这里可能需要用代理对象,替换原始对象。),保存出现循环依赖的Bean,需要提前暴露给其他Bean去依赖且还没有经过完整的生命周期的Bean 。
  3. 三级缓存:存放提前暴露的ObjectFactory。这里其实是把提前暴露的对象又封装了一层。()

解决了什么?

三级缓存的目的是为了解决循环依赖的问题。并解决了代理对象(AOP)循环依赖的问题

源码执行过程

准备配置文件

有两个Service对象,并且相互依赖着对方。

<bean name="serviceA" class="com.mfyuan.service.ServiceA">
	<property name="serviceB" ref="serviceB"/>
</bean>

<bean name="serviceB" class="com.mfyuan.service.ServiceB" >
	<property name="serviceA" ref="serviceA"/>
</bean>

执行过程

  1. 通过beanDefinitionNames来对所有bean的定义具体化。(把BeanDefinition转换成Bean对象)。

  2. AbstractBeanFactory#getBean("serviceA") 在容器中获取ServiceA

  3. AbstractBeanFactory#doGetBean("serviceA") 实例获取ServiceA的方法。

  4. DefaultSingletonBeanRegistry#getSingleton("serviceA")。记住这个方法后面经常用到。他是先从一级缓存-》二级缓存-》三级缓存依次已经判断。因为这个时候我们缓存中不存在ServiceA这个Bean,所以这里是获取不到的。所以继续往下走。

  5. DefaultSingletonBeanRegistry#getSingleton("serviceA",new ObjectFactory())。很眼熟,没错它是跟上面同样的方法名,只不过是重载方法。关键的是第二个参数ObjectFactory对象工厂。他是一个匿名内部类在高版本中也可以是lambda表达式。

    image-20231129222701663

  6. 首先从一级缓存中获取ServiceA,这里显然也是获取不到的。所以通过ObjectFactory来创建对象。而我们的ObjectFactory中只有一个方法就是createBean

    image-20231129223324775

  7. AbstractAutowireCapableBeanFactory#doCreateBean("serviceA")实际创建Bean的方法。

  8. AbstractAutowireCapableBeanFactory#createBeanInstance("serviceA")。这个时候已经把ServiceA这个Bean实例化好了,但是未初始化,且没有放入到容器中。

  9. 判断这个Bean是否需要提前暴露,如果需要。则将原始对象包装成()->getEarlyBeanReference("serviceA", mbd, bean)放入三级缓存中。

    image-20231129223856929

    image-20231129224127537

  10. AbstractAutowireCapableBeanFactory#populateBean("serviceA"),对serviceA进行属性赋值,也就是初始化。

    image-20231129224317823

  11. AbstractAutowireCapableBeanFactory#applyPropertyValues("serviceA", mbd, bw, pvs)。判断ServiceA是否依赖其他Bean,这里因为依赖ServiceB,所以会进入以下步骤。

12.主要是BeanDefinitionValueResolver#resolveValueIfNecessary("serviceB", originalValue);。这里的pv就是我们的serviceB了。然后去判断ServiceB是否还依赖其他Bean

image-20231129225439265

  1. BeanDefinitionValueResolver#resolveReference("serviceB")。这里又看到一个很熟悉的方法getBean("serviceB")也就是我们的步骤2;

    image-20231129225813312

  2. serviceBserviceA一样的步骤这里就省略详细过程。

getBean("serviceB") -> doGetBean("serviceB") -> getSingleton("serviceB") -> getSingleton("serviceB",new ObjectFactory()) 尝试在一级缓存中获取,这里也显然拿不到serviceB -> createBean("serviceB") -> doCreateBean("serviceB") -> createBeanInstance("serviceB") 实例化完成ServiceB -> addSingletonFactory(()->getEarlyBeanReference("serviceB", mbd, bean)) 将实例化但未初始化的ServiceB放到三级缓存中。 -> populateBean("serviceB") 属性赋值 -> applyPropertyValues("serviceB")-> 因为ServiceB依赖ServiceA 所以执行resolveValueIfNecessary("serviceA", originalValue)-> resolveReference("serviceA") -> this.beanFactory.getBean("serviceA") 再次去容器中获取ServiceA 又一层嵌套循环 -> doGetBean("serviceA")

  1. getSingleton("serviceA") 在容器中获取ServiceA,因为serviceA已经是正在创建的了,所以是可以从三级缓存中获取到。并把ServiceA放入二级缓存。

    image-20231129233343077

  2. AbstractAutowireCapableBeanFactory#getEarlyBeanReference("serviceA") 来获取最终公开的对象,因为我们的ServiceA他是不需要代理的所以直接返回原始对象即可。

    image-20231129233424153

  3. ServiceA这个不完整的对象放入到二级缓存中。

  4. 这里因为可以拿到ServiceA了,并通过setPropertyValues来对ServiceBserviceA进行赋值。

    image-20231129233948229

  5. initializeBean("serviceB")赋值完成后,对ServiceB进行剩下的初始化操作。

  6. addSingleton("serviceB", singletonObject)ServiceB放入一级缓存中。

    image-20231129234752127

  7. 这里也就是能到到完成的ServiceB对象了,进行执行步骤10-11中的后续步骤,也就是对ServiceAserviceB属性进行赋值。

  8. initializeBean("serviceA")赋值完成后,对ServiceA进行剩下的初始化操作。

  9. addSingleton("serviceA", singletonObject)ServiceA放入一级缓存中。

  10. 注意这里其实并没有结束,因为我们一个是通过步骤1中的循环才进入到这个过程的。因为ServiceB还没有循环到所有会继续用ServiceB来进行循环。但是因为一级缓存中都存在这些信息了所以很快就结束了。

大致的过程就是这样的。可能有些绕,结合源码啃下来还是有些收货的。

思考问题

为什么不将lambda表达式放入二级缓存呢?

如果有一个Bean同时与另外两个Bean所循环依赖呢。那是不是得从二级缓存里取两次,然后创建两次呢?这样显然是不对的。

为什么不直接将代理对象放入到二级缓存中,而是通过lambda表达式的方式存入三级缓存。

  1. Bean被创建的时候,其实并不知道,自己是否被其他Bean所依赖(也就是不知道自己是否产生了循环依赖)。

    什么时候知道自己被依赖的呢? 是当其他Bean初始化的时候扫描到依赖里的时候,才能知道。

  2. 正常流程,AOP其实是在后置处理器的去帮我们创建代理对象的。(实例化后,设置属性之后。)

    所有说我们不能一开始就把所有需要代理的对象,都给代理出来,只有产生循环依赖的时候才能把他代理出来,也就是需要把AOP操作提前。

  3. 二级缓存的结构是Map<String,Object>,如果从二级缓存中取出的对象,你还得判断一下它是否需要被代理(也就是每个对象都要判断一下)。那可能就麻烦了。

  4. 新增一个Map,并且使用lambda表达式的方法,就可以很好的解决,只有在被调用且满足条件(循环依赖&&需要被代理)的时候的时候才去创建代理对象。

总结

Spring遇到循坏依赖时,它通过使用三级缓存以及提前暴露不完整的对象来解决问题。

举例:在A实例化完成后,Spring会将他放入到三级缓存中。A此时并没有进行初始化,当A进行属性赋值的时候,如果扫描到A对象依赖B对象的话,则又会去实例化B对象,然后再把B对象放入到三级缓存中,当B进行属性赋值的时候,发现需要依赖A对象,那么这个时候就出现了循环依赖的问题了。然后从三级缓存中取出A对象,这里的A对象被包装成了一个ObjectFactroy的一个lambda表达式,这个表达式执行后会决定是否使用代理对象还是原始对象,因为属性注入的时候肯定是需要把代理后的属性给设置进去。那么当我们拿到了处理后的A对象,会将他放入到二级缓存中,此时A并没有走完所有的生命周期,并且从三级缓存中将A对象移除。到这里B的属性就注入完成了,执行剩下的生命周期后会被放入到一级缓存中,也就是单例池。然后A的属性也可以从一级缓存中取到了,然后整个循环依赖就结束了。

这三个缓存存在的目的就是为了,在容器的创建过程中,可以将某些对象提前暴露出来,从而起到打破循环的目的。

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

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

相关文章

Day46力扣打卡

最近一直在做以前的题&#xff0c;刷题量都没有怎么增长&#xff0c;感觉自己算法一直不太行&#xff0c;但也只能菜就多练了。 打卡记录 由子序列构造的最长回文串的长度&#xff08;区间DP&#xff09; 链接 第二次刷这道题&#xff0c;相比上回思路来的很快&#xff0c;但…

C# 用代码设置受保护的Excel

写在前面 在导出Excel文件的时候&#xff0c;为了防止文件内容被篡改&#xff0c;这时候就需要对Excel设置工作簿保护和工作表保护&#xff0c;本文使用的是Spire.XLS的免费版本来实现&#xff0c;免费版本是受限的&#xff0c;但是一般情况下已经够用了。 通过NuGet引入Free…

Centos7安装docker、java、python环境

文章目录 前言一、docker的安装二、docker-compose的安装三、安装python3和配置pip3配置python软链接&#xff08;关键&#xff09; 四、Centos 7.6操作系统安装JAVA环境 前言 每次vps安装docker都要看网上的文章&#xff0c;而且都非常坑&#xff0c;方法千奇百怪&#xff0c…

使用 JDBC 连接 Neo4j(头歌)

文章目录 第1关&#xff1a;连接 Neo4j &#xff08;JDBC&#xff09;任务描述相关知识完成 JDBC 环境设置连接 Neo4j 对数据进行查询 编程要求测试说明答案测试前准备代码文件 第1关&#xff1a;连接 Neo4j &#xff08;JDBC&#xff09; 任务描述 本关任务&#xff1a;使用…

Logstash使用指南

介绍 Logstash是一个开源数据收集引擎&#xff0c;具有实时管道功能。它可以动态地将来自不同数据源的数据统一起来&#xff0c;并将数据标准化到你所选择的目的地。尽管Logstash的早期目标是搜集日志&#xff0c;现在它的功能已完全不只于此。任何事件类型都可以加入分析&…

【课设大报告】 基于matlab gui设计的三极管学习系统

目录 设计背景 1.1设计目的 2.设计方法 2.1使用软件工具 2.2设计流程 3.模块界面设计及代码分析 3.1登录 3.2注册 3.3主菜单 3.4三极管介绍 3.5进制转换 3.6单位换算 3.7绘图 4.不足和改善 5.总结 设计背景 1.1设计目的 本程序旨在帮助为了帮助学生了解三极管…

借助文档控件Aspose.Words,在 C# 中比较两个 PDF 文件

在当今的数字世界中&#xff0c;管理和比较文档是一项至关重要的任务&#xff0c;尤其是在商业和法律领域。在 C# 中处理 PDF 文档时&#xff0c;Aspose.Words for .NET 提供了用于比较 PDF 文档的强大解决方案。在这篇博文中&#xff0c;我们将探讨如何在 C# 应用程序中比较 P…

笔记64:Bahdanau 注意力

本地笔记地址&#xff1a;D:\work_file\&#xff08;4&#xff09;DeepLearning_Learning\03_个人笔记\3.循环神经网络\第10章&#xff1a;动手学深度学习~注意力机制 a a a a a a a a a a a

联想SR660V2服务器重置BMC后无法设置BMC管理IP

之前登录后台显示BMC账号密码不对&#xff0c;然后把BMC重置了&#xff0c;重置以后发现无法设置IP地址 联想官方的回复是&#xff1a; 1. 机器AC上电后&#xff0c;需等待7分钟左右, 再开机&#xff0c;让bmc完全启动。 2. 在UEFI及系统下配置bmc网络&#xff0c;需等待3分钟…

人工智能 - 目标检测:发展历史、技术全解与实战

目录 一、早期方法&#xff1a;滑动窗口和特征提取滑动窗口机制工作原理 特征提取方法HOG&#xff08;Histogram of Oriented Gradients&#xff09;SIFT&#xff08;Scale-Invariant Feature Transform&#xff09; 二、深度学习的兴起&#xff1a;CNN在目标检测中的应用CNN的…

基于单片机设计的激光测距仪(采用XKC-Kl200模块)

一、前言 随着科技的不断进步和应用需求的增加&#xff0c;测距仪成为了许多领域必备的工具之一。传统的测距仪价格昂贵、体积庞大&#xff0c;使用起来不够方便。本项目采用STC89C52单片机作为主控芯片&#xff0c;结合XKC-KL200激光测距模块和LCD1602显示器&#xff0c;实现…

长沙电信大楼火灾调查报告发布:系烟头引发。FIS来护航安全

近日&#xff0c;长沙电信大楼的火灾调查报告引起广泛关注。调查发现&#xff0c;火灾是由未熄灭的烟头引发&#xff0c;烟头点燃了室外平台的易燃物&#xff0c;迅速蔓延至整个建筑。这起悲剧再次提醒我们&#xff0c;小小的疏忽可能酿成大灾难。但如果我们能及时发现并处理这…

LeetCode刷题---打家劫舍问题

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C/C》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、打家劫舍 题目链接&#xff1a;打家劫舍 题目描述 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定…

Git——使用Git进行程序开发

主要介绍个人开发提交记录的主要流程&#xff0c;包括以下内容&#xff1a; 索引- 提交的暂存区。查看工作的状态和内部变更。如何读取用于描述变更的已扩展统一diff格式。支持查询和交互的提交&#xff0c;修改提交。创建、显示和选择&#xff08;切换&#xff09;分支。切换…

FIORI /N/UI2/FLP 始终在IE浏览器中打开 无法在缺省浏览器中打开

在使用/N/UI2/FLP 打开fiori 启动面板的时候&#xff0c;总是会在IE浏览器中打开&#xff0c;无法在缺省浏览器打开 并且URL中包含myssocntl 无法正常打开 启动面板 这种情况可以取消激活ICF节点/sap/public/myssocntl

(项目已开源)社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 slider滑块 和 box3 歌词滚动区域 进行联动

(项目已开源)社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 slider滑块 和 box3 歌词滚动区域 进行联动 链接&#xff1a;https://pan.baidu.com/s/16lpEW6L5jrHfhsG7EXocLw?pwdkryy 提取码&#xff1a;kryy <!--社区求助 哪位大佬能不能帮我 将box1 audio 和 box2 s…

拒绝烂尾,Arm二期持续更新中,Arm二期Roadmap

感谢大家的支持&#xff0c;感谢大家一直以来的鼓励。更感谢大家的包容。近期又更新了很多课程&#xff0c;这次都是总线和协议相关的&#xff0c;适合软件同学基础扫盲。 硬件的同学请自行考虑&#xff0c;相信你们研究的比我这深。同时呢也在规划一些直播讨论交流课&#xff…

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《基于分布鲁棒优化的广义共享储能容量配置方法》

这个标题涉及到储能系统的容量配置方法&#xff0c;具体而言&#xff0c;是一种基于分布鲁棒优化的广义共享储能容量配置方法。让我们逐步解读&#xff1a; 基于分布鲁棒优化&#xff1a; 这表明该方法采用了一种优化技术&#xff0c;即分布鲁棒优化。分布鲁棒优化通常是指在考…

Git常用命令#merge分支合并

要查看所有分支&#xff0c;包括本地和远程仓库的分支&#xff0c;可以使用以下命令&#xff1a; 1.查看分支 1.1 查看本地分支 git branch这个命令会列出本地所有的分支&#xff0c;当前所在的分支会有 * 标记。 1.2 查看远程分支 git branch -r这个命令会列出远程仓库的分…

2023_Spark_实验二十三:Kafka的安装与基本操作

Kafka的安装与基本操作 一、前提工作 二、Kafka安装 三、Kafka基本操作 一、前提工作 必须安装了zookeeper 单机可参考&#xff1a;zookeeper单机安装与配置 集群可参考&#xff1a;zookeeper的集群安装 二、Kafka安装 上传kafka_2.11-2.4.1.tgz到/tools目录下 解压安装到…