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

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.mfbz.cn/a/206422.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

中国毫米波雷达产业分析4——毫米波雷达企业介绍

一、矽典微 &#xff08;一&#xff09;公司简介 矽典微致力于实现射频技术的智能化&#xff0c;专注于研发高性能无线技术相关芯片&#xff0c;产品广泛适用于毫米波传感器、下一代移动通信、卫星通信等无线领域。 整合自身在芯片、系统、软件、算法等领域的专业能力&#xf…

c++面试题

1.static的使用 1&#xff09;修饰局部变量&#xff1a;在函数内部使用static修饰局部变量&#xff0c;会使它成为静态局部变量。静态局部变量只会被初始化一次&#xff0c;且只有在第一次调用该函数时才会被初始化&#xff0c;之后每次调用该函数时都会保留上一次的值.从原来…

java-Swing界面简析

一、简析&#xff1a; 调用java提供的 java.swing包下的各种类可以实现界面中的各种组件(比如输入框、密码框按钮、单选框、复选框等) 二、java.swing包的关键类&#xff1a; 顶层容器&#xff1a;Jframe(窗口) 中间容器&#xff1a;Jpanel(面板) 基本控件&#xff1a; I…

倒计时(JS计时器)

<script>function countDown() {document.body.innerHTML ;//清空页面内容var nowTimer new Date(); //现在时间的毫秒数var valueTimer new Date("2024-1-1 12:00"); //用户输入年份倒计时时间毫秒数var timer (valueTimer - nowTimer) / 1000; //倒计时秒…

Python函数装饰器的用法

Python函数装饰器的用法 文章目录 1.装饰器的优点2. 使用装饰器前3. 使用装饰器后 装饰器是Python中一种强大的语法特性&#xff0c;它允许在不修改已有代码的情况下&#xff0c;对函数或类进行增强或修改。装饰器的本质是一个函数&#xff0c;它接受一个函数作为参数&#xf…

SQL Sever 基础知识 - 数据排序

SQL Sever 基础知识 - 二 、数据排序 二 、对数据进行排序第1节 ORDER BY 子句简介第2节 ORDER BY 子句示例2.1 按一列升序对结果集进行排序2.2 按一列降序对结果集进行排序2.3 按多列对结果集排序2.4 按多列对结果集不同排序2.5 按不在选择列表中的列对结果集进行排序2.6 按表…

koa2项目中封装log4js日志输出

1.日志输出到控制台 npm i log4js -D 封装log4js文件&#xff1a; 注意&#xff1a;每次都要重新获取log4js.getLogger(debug)级别才能生效 const log4js require("log4js");const levels {trace: log4js.levels.TRACE,debug: log4js.levels.DEBUG,info: log4js.…

Youtube如何做SEO关键词挖掘

做好Youtube的SEO优化&#xff0c;可以使我们的视频得到更多的展示&#xff0c;更多的点击和观看&#xff0c;就能获得更多的粉丝和流量。一方面通过视频做引流到目标网站进行转化赚钱&#xff0c;另一方面可以通过涨粉接youtube广告赚钱。要做seo最关键的一步在于关键词的挖掘…

IC设计简单概述

IC设计行业是一个高科技行业&#xff0c;有着复杂而细致的分工&#xff0c;严格的流程规范、多种不同类型的EDA工具。下面简单概述以下几个方面。 IC设计公司的分类 IC设计公司有多种分类方法。若按有无芯片生产能力来分&#xff0c;可以分为兼具设计与生产能力&#xff08;I…

(Ant X6)子组件里的流程图画布无法显示

(Ant X6)子组件里的流程图画布无法显示 问题背景&#xff1a;侧导航页面都是子组件,建模页面的画布无法显示 解决前&#xff1a; 解决后&#xff1a; 解决思路&#xff1a;点击建模菜单时再次加载对应组件 在 Vue 中&#xff0c;每个组件都有一个唯一的 key 属性。当组件的 ke…

二叉堆的实现

文章目录 堆堆的概念及性质 二叉堆的实现Heap.hHeap.c堆的初始化堆的销毁向堆中插入数据删除堆中的数据找堆顶元素判断堆是否为空Heap.c完整代码 test.c 堆 堆的概念及性质 二叉堆的实现 Heap.h #pragma once #include<stdio.h> #include<stdlib.h> #include<…

SSM校园学习助手系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 校园学习助手系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模…

HTML—列表、表格、表单

1、列表 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表 1.1 无序列表 作用&#xff1a;布局排列整齐的不需要规定顺序的区域 标签&#xff1a;ul 嵌套 li&#xff0c;ul 是无序列表&#xff0c;li 是列表条目 注意事项&#…

Microsoft Remote Desktop高效、安全、稳定的远程办公解决方案

在今天的数字化时代&#xff0c;Remote Desktop远程办公已成为许多人的日常生活。无论你是因为工作需要&#xff0c;还是因为在家中需要访问公司服务器&#xff0c;微软远程连接软件都是一个理想的选择。 微软远程连接软件Remote Desktop是一款高效、安全、稳定的远程办公解决…

(动手学习深度学习)第13章 实战kaggle竞赛:树叶分类

文章目录 实战kaggle比赛&#xff1a;树叶分类1. 导入相关库2. 查看数据格式3. 制作数据集4. 数据可视化5. 定义网络模型6. 定义超参数7. 训练模型8. 测试并提交文件 竞赛技术总结1. 技术分析2. 数据方面模型方面3. AutoGluon4. 总结 实战kaggle比赛&#xff1a;树叶分类 kagg…

数据库管理-第119期 记一次迁移和性能优化(202301130)

数据库管理-第119期 记一次迁移和性能优化&#xff08;202301130&#xff09; 1 迁移 之前因为DV组件没有迁移成功的那个PDB&#xff0c;后来想着在目标端安装DV组件迁移&#xff0c;结果目标端装不上&#xff0c;而且开了SR也没看出个所以然来。只能换一个方向&#xff0c;尝…

云计算生成式 -给你不一样的音乐推荐新体验

目录 摘要&#xff1a; 正文&#xff1a; 一、亚马逊云与生成式 AI 结合的展望/总结 二、我用亚马逊云科技生成式 AI 产品打造了什么&#xff0c;解决了什么问题 三、未来云端技术发展趋势的见解 四、云端技术未来需要解决的问题 1、如何保护数据安全和隐私&#xff1f; …

虚拟机系列:Oracle VM VirtualBox安装/更新/卸载出现 无法访问你试图使用的功能所在的网络位置

Oracle VM VirtualBox安装/更新/卸载出现 无法访问你试图使用的功能所在的网络位置 Oracle VM VirtualBox安装/更新/卸载出现 无法访问你试图使用的功能所在的网络位置Oracle VM VirtualBox安装/更新/卸载出现 无法访问你试图使用的功能所在的网络位置 在更新Oracle VM Virtua…

STC15-串口通信打印输出数据printf函数与sprintf函数

STC15-串口通信打印输出数据printf函数与sprintf函数 1.打印输出数据有二种printf函数与sprintf函数&#xff0c;不同之处有&#xff1a;&#xff08;1&#xff09;函数的声明不同&#xff08;2&#xff09;函数的功能不同&#xff08;3&#xff09;用法举例 该问题引用百度知道…

【面试HOT200】回溯篇

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招面试的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于【CodeTopHot300】进行的&#xff0c;每个知识点的修正和深入主要参…
最新文章