Spring 循环依赖处理之三级缓存设计

news2024/12/24 10:12:31

一、思考

1、Spring是如何解决循环依赖问题的?

2、为什么要使用三级缓存?二级缓存能否解决问题?

3、提前暴露对象暴露的是什么?

4、主要源码

二、循环依赖

1、介绍

 如上图,创建A之前需要先创建B,创建B之前需要先创建A,造成循环依赖。

由于A没创建完成,所以B再创建的时候再容器中获取不到A对象。

如何解决这个问题呢?

我们把创建中的对象叫半成品,创建完了的叫成本。

处于半成品状态的对象能否直接使用?不能使用。如果此时并不是暴露给外部使用,而是内部程序的调用呢?

当需要暴露给外部调用的时候,如果完成了赋值操作就不会有问题了。

如果你持有了某一个对象的引用,能否在后续步骤的时候进行赋值操作?可以。

本质是半成品状态的对象可以在中间过程中使用,实例化和初始化分开执行。

 2、关键源码实现

1、创建bean

一级缓存保存BeanName 和创建bean实例之间的关系。

二级缓存保存BeanName 和创建bean实例之间的关系,与singletonFactories的不同之处在于,当一个单例bean被放到这里之后,那就可以通过getBean方法获取到,可以方便进行循环依赖的检测。

三级缓存用于保存BeanName 和创建bean的工厂之间的关系。

ObjectFactory是一个函数式接口,在java中,方法的参数在传递的时候都是值传递,也就是说必须要传入一个具体的数据值才可以,在1.8之后,可以将lambda表达式作为一个参数传递到方法中,而不必是具体的值,那么在方法定义的时候参数的类型可以是函数式接口,在具体的方法调用的时候,lambda表达式并不会立刻执行,而是在方法中调用getObject方法才会去执行。

创建bean的关键方法

第五个是创建bean实例,最后一个是给bean实例赋值。

 源码跟踪,从spring boot 入口跟进,进入方法有很多,我这里从spring boot 启动跟进去

开始获取a实例

关键方法五执行完成后只是完成了a实例化,还没进行初始化,现在属于半成品,这里面开始进行a的创建

往下走找到此方法

进去此方法完成后三级缓存保存了次bean,此时a再三级缓存中

 2、赋值

回到上面继续往下

次方法内完成属性赋值(非容器内属性,容器内的 aware 是单独的)

进入populateBean方法,看最后一行 

PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

这里面保存b依赖的对象的kv  k是b  v是b对象【因为此时还没有创建b所以是默认RuntimeBeanReference】

 进入applyPropertyValues方法进行值处理

因为此时还没有b所以回尝试去构造b

Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);

进入方法内 上面说到没有b默认是RuntimeBeanReference

 进入方法内

这是一个大递归尝试创建b

 这里又会重复以上步骤,又会执行到关键方法五,不过此时创建的是b对象了,三级缓存中也会添加b进去,此时 

需要注意的是这一步走到applyPropertyValues方法拿到的PropertyValues pvs 中的value 仍然是RuntimeBeanReference,

执行到最后进入

新的一轮再次开始,此时拿到的name是a(b依赖a,给b赋值的是a属性),因为此时a还在三级缓存中,在二级缓存中的半成品才会暴露出来,所以这获取不到a,再次执行到关键方法二,doGetBean();

执行到关键行 

//这里会发现a已经在三级缓存中了

Object sharedInstance = getSingleton(beanName);

 分别从一二三,三级缓存中取

此时因为三级缓存中有了

所以从三级缓存中拿出来,放进二级缓存

singletonObject = singletonFactory.getObject();

这里因为传入的是内部类的实现,调用getObject();才会进行执行

此时的情况如下

此时由于已经拿到了a对象 doGetBean 就不需要调用 createBean创建对象,

继续执行到这里,就可以直接从二级缓存中获取到a对象了

此时b获取到a对象之后,就可以进行赋值了,由于a已经存在所以这里获取到的不在是RuntimeBeanReference而是a的实例,注意此时是从二级缓存中获取到的,a属于半成品。

有了a对象之后就可以直接对b进行赋值了 applyPropertyValues方法中赋值

执行完成之后b里面的a属性就有值了 ,此时b是成品如下

 

b执行完了之后,需要进行缓存再次进行缓存。因为此时b已经是成品所以直接放进一级缓存。

getSingleton

 以上创建b对象的原因是为了给a对象赋值,此时b已经变成成品了。

此时给a再次赋值的时候b已经获取到了

返回

此时resolveValue也有了,是一个b对象

 此时就可以给a赋值了

此时 a也创建完了

 a执行完了之后,需要进行缓存再次进行缓存。因为此时a已经是成品所以直接放进一级缓存。

getSingleton

 此时a和b都创建完了,但是b是被迫创建的。

再次回到这循环,上面所有的只是循环了a,此时进行b的循环,由于a循环的时候,b已经创建好了,所以本次循环很顺利的完成了。 

此时正式创建完成。

 三、思考

1、三个map结构分别存储什么对象

一级缓存:成品对象
二级缓存:半成品对象
三级缓存:lambda表达式

2、三个map结构在查找对象的时候顺序是什么样的?

1,2,3

3、如果只有一个map结构,能否解决循环依赖的问题?

理论上来说是可以的,但是实际操作的时候会有问题,一级缓存和二级缓存的区分点在于存储的对象类型不一样,当只有一个map的时候,就意味着成品对象和半成品对象放到了一起,半成品对象是不能够直接暴露给外部使用的,因为会有空指针的问题,所以如果非要用一个map存储就意味着要添加标识,来标注是成品对象还是本成品对象,如果按照这样的方式来设计代码,会很不优雅,所以可以直接用两个map解决这个问题,不需要一个。

4、如果只有两个map,可以解决循环依赖问题吗

可以,但是有前提条件:在对象创建过程中不能有代理对象

当存在代理对象的时候,抛出一下异常。

Exception in thread "main"org.springframework.beans.factory.BeanCurrentlylnCreationException:

Error creating bean with name 'a': Bean with name 'a' has been injected into other beans (b] in its rawversion as part of a circular reference, but has eventually been wrapped.

This means that said other beans do not use the final version of the bean.

This is often the result of over-eager type matching - consider using 'getBeanNamesForlype' with the'allowEagerInit' flag turned off for example.

线程“main”org.springframework.beans.factory.BeanCurrentlynCreateException中出现异常:
创建名为“a”的bean时出错:名为“a”的bean已作为循环引用的一部分注入到原始版本中的其他bean(b]中,但最终已被包装。
这意味着所述其他bean不使用bean的最终版本。
这通常是过度渴望类型匹配的结果——例如,考虑在关闭“allowEagleInit”标志的情况下使用“getBeanNamesForlype”。

5、为什么必须要使用三级缓存来解决循环依赖问题?为什么三级缓存可以解决带有代理对象的循环依赖问题?

1,同一个容器中,能否出现同名的不同对象?

不能

2,如果出现了同名的不同对象,应该怎么办?刚开始创建出原始对象,后续创建出了代理对象
如果在创建过程中出现了同名的不同对象,那么后面创建的对象要覆盖前面创建的对象

在这里会被覆盖



3,那么为什么要使用lambda表达式这样的方式,为什么要加入三级缓存呢?

对象的属性赋值是在哪个方法里完成的?populateBean方法
代理对象的创建是在哪个方法里完成的?BeanPostProcessor的后置处理方法里完成的
他们两个方法谁先执行,谁后执行呢?先执行populateBean,然后执行后置处理方法

也就是说,在进行对象的属性赋值的时候,代理对象还没有创建出来,那么属性的赋值只能赋原始对象,而在后续的步骤中又创建出了代理对象,此时的代理对象会有赋值的过程吗?不会,所以会出现一个错误
this means that said other beans do not use the final version of the bean
就是说赋值是原始对象,而最终留下来的是代理对象,所以导致没有使用最终版本的bean对象

如何解决?

将代理对象的创建过程提前执行,也就是说在进行对象赋值的时候,必须要唯一性的确定出到底是原始对象还是代理对象,这个方法是在getEarlyBeanReference方法里执行的,是在populateBean方法的调用逻辑里操作的


为什么要使用lambda表达式?

lambda表达式相当于是延迟执行,因为此方法并不会在方法调用的时候立刻执行,而是在对象必须要进行赋值的那一刻执行,也就是说在对象赋值的前一刻确定出了最终版本的bean对象。 

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

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

相关文章

C++:IO流

CIO流 C系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类 注意: 1. cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输 入过多,会留在那儿慢慢…

今年SMETA审核费用即将涨价

【今年SMETA审核费用即将涨价】 SMETA全称( Sedex Members Ethical Trade Audit ),即Sedex会员社会道德贸易审核,它是Sedex发起的一种负责任的供应链审计方法/项目。 Sedex是一个全球性的责任商业平台,SMETA是审核方法…

大白话chatGPT GPT的发展区别

大白话chatGPT & GPT的发展区别 从GPT名字理解chatGPTchatGPT三步曲GPT-1到GPT-4GPT-1~GPT-4的相同点GPT-1~GPT-4的区别GPT-1——pre-training fine-tune,自监督学习无监督预训练有监督微调GPT-2——zero-shot,无监督学习,多任务学习GPT…

机器学习快速入门1(机器学习概念与数据预处理)

文章目录 1.机器学习介绍2 机器学习7大步骤3.数据预处理八大策略4 缺省值、异常值、重复值处理**rand、randn、randint区别**normal与randn缺失值处理判断缺失值删除缺失行填充缺失值 重复值处理异常值处理 5 抽样VS全量数据为什么需要抽样简单随机抽样等距抽样分层抽样整群抽样…

【PR 基础】轨道遮罩键、交叉溶解的简单使用

在上篇博客(【PR 基础】裁剪工具的简单使用)介绍了裁剪效果的使用,本篇博客在上篇的基础上继续添加 轨道遮罩键、交叉溶解的效果。 效果 步骤 1.可以先将恢复裁剪区域的关键帧删除 2. 接下来添加字幕,点击 新建-》旧版标题 点击…

Ceph入门到精通-Ceph 编排器简介

第 1 章 Ceph 编排器简介 作为存储管理员,您可以将 Ceph 编排器与 Cephadm 实用程序搭配使用,能够发现设备并在 Red Hat Ceph Storage 集群中创建服务。 1.1. 使用 Ceph Orchestrator Red Hat Ceph Storage Orchestrators 是经理模块,主要…

深入JVM了解Java对象实例化过程

文章目录 一、对象创建方式二、对象产生步骤1、判断对象是否已经加载、链接、初始化2、为对象分配内存空间3、处理并发问题3.1 TLAB 4、初始化零值5、完善对象内存布局的信息6、调用对象的实例化方法 <init>7、总结 三、对象的内存布局1、对象头1.1 运行时元数据&#xf…

大学生创业项目-校园外卖的创业优势在哪里?

在当今的外卖行业中&#xff0c;校园外卖已成为外卖行业的垂直分类领域。 与我们通常使用的美团、饿了么平台不同&#xff0c;校园外卖平台需要招聘校园学生和校园内外人员兼职作为校园骑手&#xff0c;完成“最后一公里”的外卖送餐方式。 对于平台运营商来说&#xff0c;配…

生成树协议三姐妹:STP、RSTP 和 MSTP,附思科和华为双厂商命令示例

在计算机网络中&#xff0c;为了保证网络拓扑结构的稳定性和可靠性&#xff0c;需要采用一些协议进行网络的管理和控制。其中&#xff0c;STP、RSTP 和 MSTP 是三种常用的网络管理协议。本文将分别介绍这三种协议&#xff0c;并且使用华为、思科两家厂商作为案例给出相应的命令…

( “树” 之 Trie) 677. 键值映射 ——【Leetcode每日一题】

知识点回顾 &#xff1a; Trie&#xff0c;又称前缀树或字典树&#xff0c;用于判断字符串是否存在或者是否具有某种字符串前缀。 ❓677. 键值映射 难度&#xff1a;中等 设计一个 map &#xff0c;满足以下几点: 字符串表示键&#xff0c;整数表示值返回具有前缀等于给定字…

Scrapy框架 -- 深度爬取并持久化保存图片

一、新建一个Scrapy项目daimg scrapy startproject daimg 二、进入该项目并创建爬虫文件daimgpc cd daimg scrapy genspider daimgpc www.xxx.com 三、修改配置文件settings.py ROBOTSTXT_OBEY False LOG_LEVEL ERROR USER_AGENT "Mozilla/5.0 (Windows NT 10.0; …

Git快速入门

Git快速入门 版本控制什么是版本控制常见的版本控制工具版本控制分类Git与SVN的主要区别 聊聊Git的历史Git环境配置软件下载启动GitGit配置 Git基本理论&#xff08;重要&#xff09;三个区域工作流程 Git项目搭建创建工作目录与常用指令本地仓库搭建克隆远程仓库 Git文件操作文…

Springsecurity课程笔记06-13章基于数据库的方法授权

动力节点Springsecurity视频课程 6 密码处理 6.1 为什么要加密&#xff1f; csdn 密码泄露事件 泄露事件经过&#xff1a;https://www.williamlong.info/archives/2933.html 泄露数据分析&#xff1a;https://blog.csdn.net/crazyhacking/article/details/10443849 6.2加密…

平均薪资28K,测试开发的涨薪史,给我看哭了...

金三银四的涨薪季要来了&#xff0c;看着身边的同事有的晋升&#xff0c;有的收获30%的涨薪&#xff0c;他们都拥有哪些影响涨薪的硬核技能呢&#xff1f;互联网行业的高薪是众所周知的&#xff0c;而测试作为互联网公司越来越重视的技术开发模块&#xff0c;薪资收入同样一路走…

Tomcat部署与优化

前言 Tomcat是一款免费、开放源代码的Web应用服务器&#xff0c;是Apache软件基金会的一个核心开源项目&#xff0c;属于轻量级应用服务器&#xff0c;通常意义上的 Web 服务器接受请求后&#xff0c;只是单纯地响应静态资源&#xff0c;如 HTML 文件&#xff0c;图片文件等&a…

深入探究C++中的仿函数和迭代器——提升你的STL技能

&#x1f4d6;作者介绍&#xff1a;22级树莓人&#xff08;计算机专业&#xff09;&#xff0c;热爱编程&#xff1c;目前在c&#xff0b;&#xff0b;阶段>——目标Windows&#xff0c;MySQL&#xff0c;Qt&#xff0c;数据结构与算法&#xff0c;Linux&#xff0c;多线程&…

若依/RuoYi-Vue,若依管理系统-启动步骤

若依RuoYi-Vue前后端项目启动流程_若依前端怎么启动_primary taste_mm的博客-CSDN博客若依官网&#xff1a;RuoYi 若依官方网站 |后台管理系统|权限管理系统|快速开发框架|企业管理系统|开源框架|微服务框架|前后端分离框架|开源后台系统|RuoYi|RuoYi-Vue|RuoYi-Cloud|RuoYi框架…

进驻Lidl利多超市利器—— EDI

Lidl利多超市是源自德国的跨国零售企业&#xff0c;成立于1973年&#xff0c;发展迅速&#xff0c;目前在欧洲拥有10,800多家门店&#xff0c;覆盖29个国家。Lidl的业务范围包括食品、饮料、家庭用品、家具、电器等多个品类。Lidl一直致力于提供高性价比的商品&#xff0c;以满…

FIT2CLOUD飞致云发布开源轻量级云管平台CloudExplorer Lite

2023年4月21日&#xff0c;中国领先的开源软件公司FIT2CLOUD飞致云正式发布开源轻量级云管平台项目CloudExplorer Lite。CloudExplorer Lite&#xff08;https://github.com/CloudExplorer-Dev&#xff09;脱胎于飞致云创始软件产品CloudExplorer多云管理平台&#xff0c;支持对…

图表示学习算法学习

struc2vec: Learning Node Representations from Structural Identity learning latent representations for the structural identity of nodes. &#xff1a; 从结构特征中学习节点潜在表示 node representation : 节点表示 structural identity : 结构特征 struct2Vec是一个…