Spring怎么用三级缓存解决循环依赖问题
创始人
2025-05-30 07:34:49
0

承接上文Spring Bean实例化和初始化的过程

循环依赖问题

A类中有b对象,B类中有a对象,互相引用,

在spring整个生命周期里面,所有bean默认都是单例的,通过反射创建一个具体的对象,设置对象属性值。

当前这种情况,要么先创建a,要么先创建b。

如果a对象创建好了,b对象还没有创建,此时需要把b对象也完成实例化,才能给a对象完成一个赋值。

在spring中除了实例化,还有一个初始化 ,这里面就会牵扯到一个循环引用的问题。

怎么解决循环依赖的问题?

所谓实例化,就是在堆中开辟一块空间,实例化要么通过反射要么new的方式。

初始化的时候给某些类的属性进行赋值,设置属性有2种方式,set和构造方法,

set是额外的一个独立的方法,用来设置属性值;

构造方法是既创建了对象又给属性赋值。

set可以解决循环依赖问题,

构造方法不能解决循环依赖。

set先有对象,再给对象set即分成2步。

构造方法创建出对象之后立马给对象属性值赋值,将两步合成一步。

把步骤分开可以解决这个问题,步骤合起来是没有办法解决这个问题的。

当使用三级缓存解决循环依赖问题的时候,本质的点在于将实例化和初始化分开处理。

提前暴露对象 :已经完成实例化但是未完成初始化的对象。

把这两个类放入spring之后,会帮我们进行具体对象的创建,创建的时候对象都是单例的。

单例意味着整个空间中有且仅有这一个对象。

两个bean不可能同时创建,而是一个一个来创建的。

第一步先实例化a对象,此时只是堆空间开辟,并没有设置属性值,此时不是一个完整的对象,是半成品,当有了半成品之后,要初始化a对象,就是给a对象中的b属性完成赋值操作,此时要给b属性赋值了,b是一个完全独立的对象,所以此时要去beanfactory或spring容器中查找b对象,

判断是否有b对象,如果有直接赋值,没有的话,要完成b对象的实例化,

先实例化b对象,如果能成功,说明只是在堆中分配了一块内存空间,并没有设置一个具体的属性值,这里也是半成品,接下来要完成b对象的初始化操作即给b对象中的a属性赋值,去spring容器中查找a对象,如果有的话,直接赋值返回;如果没有怎么办?再次去实例化a,

此时就变成一个环了,永远结束不了。

此时找a对象的时候是否需要把a再重新实例化取决于当前容器里面是否有a,此时a已经完成了实例化,因为之前已经用过了,只不过它不是一个完整的对象,只是一个半成品对象,既然是半成品能不能在半成品里面直接获取到?

直接可以把对应的半成品放到spring容器里面来,半成品放进来之后,会思考一件事情,里面没有属性还不能使用,就意味着在后续的一个处理过程中,最终还是要把当前的a对象完成初始化操作的。

此时最起码已经找到半成品了或者容器里面已经有对象了,只不过这个对象还不能直接拿过来使用,还要进行相关的其他操作。

怎么把半成品放到容器里面?

为什么要设计三级缓存的存在? 这三个缓存有什么不同的点?

除了容量和类型之外,不同的地方在于整个集合的范型,一级缓存和二级缓存是Object,三级缓存里面放的是ObjectFactory,

ObjectFactory是函数式接口,仅有一个方法,可以往里面传入一个lamda表达式或传入一个匿名内部类,可以通过getObject方法执行相关的具体逻辑,

refresh包含的13个方法是spring处理的核心,循环依赖涉及到bean的实例化过程,所以进行相关实例化的时候,要看finishBeanFactoryInitialization(beanFactory)方法,调用beanFactory.preInstantiateSingletons()。

通过debug的方式看bean实例化的过程

当开始实例化的时候,并没有指定要实例化几个bean,scope默认是单例(singleton),

当执行到finishBeanFactoryInitialization(beanFactory)这一步的时候,配置文件中的2个bean已经被加载了,放到了beanDefinitionNames map集合中,

从这行开始进行实例化,

当前空间里包含了2个对象,所以此时要去循环的进行创建。

要么先a后b,要么先b后a,

若a先实例化,a先获取BeanDefinition,判断下是否是抽象的(a不是抽象的),判断下是否是单例的(a是单例的),是否是懒加载的(a不是懒加载的),

所以条件为true,再判断有没有实现FactoryBean接口,没有,则执行getBean方法(spring在创建对象之前,每次都是先从容器中查找,找不到再创建),

要去容器里面进行一个查找工作,先去一级缓存进行查找,判断当前这个单例对象是否在被创建的过程当中,

a此时还没有开始创建,所以条件为false。

lambda表达式和匿名内部类是一样的,里面最核心的方法是createBean,

先从一级缓存singletonObjects中获取,一级缓存没有的话,调用lambda表,当你看到getObject的时候,它实际上调用的createBean,

执行到这里,就获取到了a对象了(A@1755),只是b属性是null即还没有给b属性赋值,此时的bean对象是一个半成品,还没有进行初始化。

当a实例化完成之后,要完成整体对象的填充即属性填充(给b填充属性值),

只有调用getObject才会执行lamda表达式。

先判断一级缓存singletonObjects是否包含a(此时不包含),条件为true,将a对应的lambda表达式放三级缓存singletonFactories中,

registeredSingletons这个集合表示哪些对象已经注册过了。

这一步就是完整的属性填充方法,

开始填充属性值,给a对象里面的b填充属性,

此时属性名称是b,属性值不是bean对象,而是运行时的bean引用,

对当前这个值进行相关的处理工作,

判断value是什么类型,进行相应的解析,

去容器中找b,对应着这一步,

一级缓存没有b对象,b对象也没有在创建,条件不满足,直接返回。

接下来,实例化b对象,

调用createBean->doCreateBean方法,

实例化b,

此时已经有了b对象了,还没有给b初始化,所以a属性是空的,此时b是半成品对象,

此时这两个对象都是半成品对象,只完成了实例化并没有初始化操作,

此时b已经完成了实例化,接下来给b填充属性,

往三级缓存加b的lamda函数,

给b对象里面的a属性填充,因为a还是空的,

从容器中获取a,对应着这一步,

截止目前,已经是第3次调用getBean方法了:

第一次创建a的时候,第二次创建b的时候,第三次给b里面的a属性赋值的时候,

一级缓存中没有,a对象现在是在被创建过程中,再从二级缓存再获取,二级缓存没有,去三级缓存中获取,

根据a去三级缓存中获取到a所对应的lambda表达式,调用getObject方法,实际上执行的是lambda表达式,

这里的bean对象是A@1755,属性b为null,这个时候取到a对象了,只不过此时的a对象是一个半成品对象,然后删除三级缓存中的a对应的lambda表达式,

刚才是为了给b里面的a属性赋值:先实例化a再初始化a,初始化a的时候找b,先实例化b,再初始化b,初始化b的时候要去容器里面找a,a找回来了,赋值给b中的a属性,当赋值完成之后,相当于把a的半成品取回来,完成了整体的赋值操作,意味着b的实例化和初始化都完成了。

一开始为了给a进行初始化,现在b完成了,该去初始化a了,此时a是半成品状态,b是成品状态。

往一级缓存放入对象B@2152,

然后把二级和三级缓存删掉,

然后给a对象的b属性赋值,

执行完这一步就得到了A@1755,属性b为B@2151,现在完成了a对象的初始化工作,a是成品对象了,然后放入一级缓存,把二级三级缓存清理掉,

第一次for循环是为了创建a,此时a对象实例化完成了,第二次循环是要实例化b了,而此时一级缓存已经有b了,直接返回b对象。

小结

三级缓存解决循环依赖问题的关键是通过提前暴露对象来解决,就在于实例化和初始化分开操作,在中间过程中给其他对象赋值的并不是一个完整的对象,而是半成品对象。

先实例化a,再初始化a,然后再实例化b,再初始化b,要找到对应的a,此时取的a是一个半成品状态,所以就解决了循环依赖的问题。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
JAVA多线程知识整理 Java多线程基础 线程的创建和启动 继承Thread类来创建并启动 自定义Thread类的子类&#...
【洛谷 P1090】[NOIP... [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G ...
国民技术LPUART介绍 低功耗通用异步接收器(LPUART) 简介 低功耗通用异步收发器...
城乡供水一体化平台-助力乡村振... 城乡供水一体化管理系统建设方案 城乡供水一体化管理系统是运用云计算、大数据等信息化手段࿰...
程序的循环结构和random库...   第三个参数就是步长     引入文件时记得指明字符格式,否则读入不了 ...
中国版ChatGPT在哪些方面... 目录 一、中国巨大的市场需求 二、中国企业加速创新 三、中国的人工智能发展 四、企业愿景的推进 五、...
报名开启 | 共赴一场 Flu... 2023 年 1 月 25 日,Flutter Forward 大会在肯尼亚首都内罗毕...
汇编00-MASM 和 Vis... Qt源码解析 索引 汇编逆向--- MASM 和 Visual Studio入门 前提知识ÿ...
【简陋Web应用3】实现人脸比... 文章目录🍉 前情提要🌷 效果演示🥝 实现过程1. u...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
windows安装JDK步骤 一、 下载JDK安装包 下载地址:https://www.oracle.com/jav...
分治法实现合并排序(归并排序)... 🎊【数据结构与算法】专题正在持续更新中,各种数据结构的创建原理与运用✨...
在linux上安装配置node... 目录前言1,关于nodejs2,配置环境变量3,总结 前言...
Linux学习之端口、网络协议... 端口:设备与外界通讯交流的出口 网络协议:   网络协议是指计算机通信网...
Linux内核进程管理并发同步... 并发同步并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理...
opencv学习-HOG LO... 目录1. HOG(Histogram of Oriented Gradients,方向梯度直方图)1...
EEG微状态的功能意义 导读大脑的瞬时全局功能状态反映在其电场结构上。聚类分析方法一致地提取了四种头表面脑电场结构ÿ...
【Unity 手写PBR】Bu... 写在前面 前期积累: GAMES101作业7提高-实现微表面模型你需要了解的知识 【技...