当前位置: 首页 > news >正文

Spring解决循环依赖

Spring框架为了解决循环依赖问题,设计了一套三级缓存机制:

  • 一级缓存singletonObjects:这个是最常规的缓存,用于存放完成初始化好的bean,如果某个bean已经在这个缓存了直接返回。
  • 二级缓存earlySigletonObjects:这个用于存放早期暴露出来的bean,就是那些创建出来还没有初始化好的bean,这样做的目的就是为了bean创建过程中能提前暴露出来,方便解决循环依赖的问题。
  • 三级缓存 singletonFactories:这个缓存存放的是bean的工厂对象,这个工厂对象负责bean的实例,当一个bean创建时,它的工厂对象会被放入缓存中。

Spring解决循环依赖

三级缓存

  • singletonObjects:用于存放完全初始化好的 bean,从该缓存中取出的 bean 可以直接使用

  • earlySingletonObjects:提前曝光的单例对象的cache,存放原始的 bean 对象(尚未填充属性),用于解决循环依赖

  • singletonFactories:单例对象工厂的cache,存放 bean 工厂对象,用于解决循环依赖

整体流程
  1. 首先A完成初始化第一步先将自己提前曝光出来(通过ObjectFactory将自己提前曝光出来),在初始化的时候,发现自己需要依赖B,就开始尝试get(B),这时候发现B还没有创建;

  2. B走创建流程,在B初始化的时候依赖C,C还没有创建出来;

  3. C开始初始化,在C初始化的时候发现自己依赖A,于是尝试get(A),这时候由于A已经添加到缓存中了(一般都是添加到三级缓存中singletonFactory),通过ObjectFactory提前曝光,所以可以通过ObjectFactory#getObject()获取到A对象。C拿着A对象后顺利初始化,然后自己添加到一级缓存中;

  4. 回到B,B也拿到C对象,完成初始化,A可以顺利拿到B,这里整个链路已经完成初始化过程了。

关键字:三级缓存,提前曝光

再来一个例子

假设现在有两个bean,一个A,一个B;

Spring容器开始创建A对象,会先去一级缓存中查看是否有BeanA的实例,如果没有就会创建一个A的实例,并将其工厂对象放入三级缓存中,然后BeanA 的创建因为需要注入B而被挂起,Spring开始创建BeanB对象。

BeanB同样回去一级缓存中查找是否存在B实例,由于还没有创建,Spring会将bean B的半成品放入二级缓存,继续创建买这个B需要依赖A,由于A的工厂对象已经放在三级缓存中了,spring可以直接获取三级对象中的beanA的工厂对象,通过它来创建beanA的实例。

这样,即使两个 beans 相互依赖,Spring 也能够通过三级缓存机制成功地创建它们,解决了循环依赖的问题。

仅有二级缓存无法解决涉及AOP代理的循环依赖问题。

为什么二级缓存不可以

二级缓存earlySingletonObjects用于存储半成品的Bean实例,即那些已经被实例化但尚未完成初始化(如属性填充和方法调用)的Bean。这个缓存允许Spring在Bean的创建过程中就能提前暴露出来,以便于解决循环依赖的问题。然而,如果只有二级缓存,当涉及到AOP代理时,问题就来了。

AOP代理的生成是在Bean的初始化阶段完成的,这意味着在Bean的所有属性都被设置之后。如果一个Bean需要被代理,那么在代理之前,Spring会尽量从缓存中获取到原始的Bean实例,以避免在代理过程中出现循环引用的问题。但是,如果只有二级缓存,那么在Bean初始化之前,我们无法从缓存中获取到代理对象,因为二级缓存中存储的是尚未初始化的Bean实例,而不是代理对象。

例子

假设有两个Bean,A和B,它们相互依赖,并且A需要被AOP代理。在Spring的创建过程中,首先会创建A的实例并将其放入二级缓存中。然后,当尝试创建B并注入A时,会发现A还没有完成初始化,因此无法生成A的代理对象。这样就会导致循环依赖的问题无法被解决。

而三级缓存中的singletonFactories存储的是Bean的工厂对象,可以在Bean初始化之前就生成代理对象,并将其放入一级缓存singletonObjects中。这样,即使Bean之间存在循环依赖,Spring也能够通过三级缓存机制成功地创建它们,解决了循环依赖的问题。

总的来说,三级缓存机制是Spring为了在保持设计原则的同时,解决循环依赖和AOP代理的问题而设计的。二级缓存虽然可以解决部分循环依赖的问题,但在面对AOP代理时就显得力不从心了。因此,Spring需要三级缓存来确保在复杂情况下依然能够正常工作。

http://www.lryc.cn/news/362477.html

相关文章:

  • RUST运算符重载
  • 描述一下 Array.forEach() 循环和 Array.map() 方法之间的主要区别
  • 在GEE中显示矢量或栅格数据的边界(包含样式设计)
  • django使用fetch上传文件
  • linux安装docker步骤
  • Unity DOTS技术(一)简介
  • 深度解读ChatGPT基本原理
  • python实现——分类类型数据挖掘任务(图形识别分类任务)
  • 【安卓跨进程通信IPC】-- Binder
  • 大数据之Schedule调度错误(一)
  • DiffIR论文阅读笔记
  • prometheus+alertmanager+webhook钉钉机器人告警
  • ctfshow 年CTF web
  • 原型链、闭包、手写一个闭包函数、 闭包有哪些优缺点、原型链继承
  • linux中SSH_ASKPASS全局变量的作用
  • 9 -力扣高频 SQL 50 题(基础版)
  • TCP的重传机制
  • pg 数据库,获取时间字段值的具体小时,赋值给其他字段
  • 做视频号小店什么类目最容易爆单?其实,弄懂这三点就会选品了
  • Nginx作为下载站点
  • vue3简单快速实现主题切换功能
  • 国联易安:网络反不正当竞争,要防患于未然
  • Linux 网络配置 01
  • 快速入门C++正则表达式
  • java —— 缓冲字符输入流/缓冲字符输出流
  • blender从视频中动作捕捉,绑定到人物模型
  • 掘金滑块验证码安全升级,继续破解
  • 数据结构练习题——Java实现
  • 行为设计模式之状态模式
  • 找回以前的视频:技术与实践3个指南