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.mzph.cn/news/187281.shtml

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

相关文章

jupyter notebook 添加环境与删除环境

添加环境 一、查看conda现有的环境 打开 Anaconda Powershell Prompt 输入以下代码&#xff0c;查看全部环境&#xff1a;conda env list 可以看到如下已经配置的环境变量&#xff1a; 二、激活现有环境 在 Anaconda Powershell Prompt 继续输入&#xff0c;激活环境&#…

Python之Appium 2自动化测试(Android篇)

一、环境搭建及准备工作 1、Appium 2 环境搭建 请参考另一篇文章: Windows系统搭建Appium 2 和 Appium Inspector 环境 2、安装 Appium-Python-Client&#xff0c;版本要求3.0及以上 pip install Appium-Python-ClientVersion: 3.1.03、手机连接电脑&#xff0c;并在dos窗口…

13-Vue基础之自定义指令与插槽的使用

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大二在校生 &#x1f921; 个人主页&#xff1a;坠入暮云间x &#x1f43c;座右铭&#xff1a;懒惰受到的惩罚不仅仅是自己的失败&#xff0c;还有别人的成功。 &#x1f385;**学习目标: 坚持每一次的学习打卡 文章…

[c++]——string类____详细初步了解string类的运用

在成为大人的路上喘口气. 目录 &#x1f393;标准库类型string &#x1f393;定义和初始化string对象 &#x1f4bb;string类对象的常见构造 &#x1f4bb;string类对象的不常见构造 &#x1f4bb;读写string对象 &#x1f393; string类对象的修改操作 &#x1f4…

openGauss学习笔记-135 openGauss 数据库运维-例行维护-检查openGauss健康状态

文章目录 openGauss学习笔记-135 openGauss 数据库运维-例行维护-检查openGauss健康状态135.1 检查办法135.2 操作步骤135.3 异常处理 openGauss学习笔记-135 openGauss 数据库运维-例行维护-检查openGauss健康状态 135.1 检查办法 通过openGauss提供的gs_check工具可以开展o…

List系列集合

List系列集合特点&#xff1a;有序&#xff0c;可重复&#xff0c;有索引 ArrayList&#xff1a;有序&#xff0c;可重复&#xff0c;有索引 LinkedList&#xff1a;有序&#xff0c;可重复&#xff0c;有索引 &#xff08;底层实现不同&#xff01;适合的场景不同&#xff01;…

java实战(四):编写学生信息管理系统页面·

1.要求 编写程序 实现表格的输入和编辑功能。界面如下&#xff1a; 1、用户按插入键后&#xff0c;把学号、姓名和成绩插入到最后一行&#xff0c;序号显示当前的行号。 2、当用户选中表格的某一行时&#xff0c;按删除按钮&#xff0c;则这一行从表格中删除 3、编辑功能&am…

OpenCV中八种不同的目标追踪算法

引言 目标跟踪作为机器学习的一个重要分支&#xff0c;加之其在日常生活、军事行动中的广泛应用&#xff0c;受到极大的关注。在AI潮流中&#xff0c;大家对于深度学习&#xff0c;目标跟踪肯定都会有过接触了解&#xff1a;在GPU上通过大量的数据集训练出自己想使用的垂直场景…

sqli-labs(9)

45. 不会显示报错信息通过or 1验证 在密码处输入)or(1 登录成功 )union select 1,2,3 # )union select 1,database(),3 # )union select 1,(select group_concat(table_name) from information_schema.tables where table_schemasecurity),3 # )union select 1,(select gro…

学习记录PCL-1 通过哈希表进行三维点云的虚拟格网划分

直接对整个场景的点云进行特征提取&#xff0c;效果很差&#xff0c;因此通过划分区域格网进行划分。格网划分有很多种方式&#xff0c;在这里尝试使用哈希表进行格网链接&#xff0c;后续通过在每个格网内基于点云特征进行提取。 参考博客&#xff1a; 点云侠的PCL 点云分块_p…

ESP32-Web-Server编程- 通过文本框向 Web 提交数据

ESP32-Web-Server编程- 通过文本框向 Web 提交数据 概述 前述章节我们通过简单 HTML、AJAX、Websocket、SSE 在网页上显示数据&#xff0c;通过网页上的按钮控制 ESP32 的行为。从本节开始&#xff0c;我们将进一步了解通过网页与 ESP32 进行交互的方法。 实现更复杂的交互功…

【OJ比赛日历】快周末了,不来一场比赛吗? #12.02-12.08 #15场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-12-02&#xff08;周六&#xff09; #4场比赛2023-12-03…

nginx部署多个vue或react项目

下载nginx(tar.gz) nginx: download(官方地址) 部署nginx # 进入nginx压缩包所在目录 cd /usr/nginx# 解压 tar -zxvf nginx-1.25.3.tar.gz# 安装nginx的相关依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel# 生成Makefile可编译文件 cd /usr/ng…

Electron+Ts+Vue+Vite桌面应用系列:TypeScript常用时间处理工具

文章目录 1️⃣ 时间处理工具1.1 格式化时间1.2 把时间戳改成日期格式1.3 Day.js 工具类使用1.4 date-fns 工具类使用 优质资源分享 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/134712978 ElectronTsVueVite桌面应用…

华为全屋智能5.0,无为而“智”

在赖特西塔里埃森混凝土墙的中心壁龛里&#xff0c;一块铜牌上刻着一些英文&#xff0c;意思是“建筑的意义不是屋顶和墙&#xff0c;而是人们生活于其中的空间”。 这句话&#xff0c;取自老子《道德经》中的“凿户牖以为室&#xff0c;当其无&#xff0c;有室之用”。 《理想…

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

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

VIR-SLAM代码分析3——VIR_VINS详解之estimator.cpp/.h

前言 续接上一篇&#xff0c;本本篇接着介绍VIR-SLAM中estimator.cpp/.h文件的函数&#xff0c;尤其是和UWB相关的相比于VINS改动过的函数&#xff0c;仍然以具体功能情况代码注释的形式进行介绍。 重点函数介绍 优化函数&#xff0c;代码是先优化&#xff0c;后边缘化。 …

mysql 日志分析

程序启动标志 可以直接全局搜索&#xff0c;查看启动了几次 可以看到总共11次&#xff0c;当前是第2次 如何判断mysql是正常关闭&#xff0c;手动启动的 下图中启动之前出现 Shutdown complete打印说明启动之前是正常关闭的

Serilog .net下的新兴的日志框架

Serilog .net下的新兴的日志框架 1.Serilog简介 Serilog 是针对 .NET 应用程序的流行日志记录框架。它以其灵活性、易用性和可扩展性而闻名。借助 Serilog&#xff0c;开发人员可以轻松记录应用程序中的事件、错误和消息。它支持结构化日志记录&#xff0c;能够以结构化格式存…

贪心 55. 跳跃游戏 45.跳跃游戏 II

55. 跳跃游戏 题目&#xff1a; 给定非负数组&#xff0c;初始位置在数组第一格&#xff0c;数组值是可以选择的最大跳跃步数&#xff0c;判断能不能达到数组末尾。 示例 1: * 输入: [2,3,1,1,4] * 输出: true * 解释: 我们可以先跳 1 步&#xff0c;从位置 0 到达 位置 1,…