Spring 如何解决循环依赖问题 - 三级缓存

1. 什么是循环依赖问题 ?

        循环依赖问题是指对象与对象之间存在相互依赖关系,而且形成了一个闭环,导致两个或多个对象都无法准确的完成对象的创建和初始化。

两个对象间的循环依赖:

多个对象间的循环依赖 :

解决 Spring 中的循环依赖有两个前提条件:

  1. 存在相互依赖的 Bean 必须是单例的 Bean;
  2. 依赖注入 Bean 的方式不能是构造方法注入。

为什么要满足条件 1:

        如果说相互依赖的 Bean 不是单例的,那么 A 需要 B,就得重新 new 一个  B,B 需要 A,就得重新 new 一个 A,新 new 出来的 A 又需要 B,新 new 出来的 B 又需要 A,又进入最初的步骤了,这样下去将会无穷尽也,就根本解决不了了,所以要求存在相互依赖的 Bean 必须是单例的 Bean。

为什么要满足条件 2:

在了解这个原因之前,必须得先了解 Bean  的生命周期,不太清楚的可以先去看看这篇博客:https://blog.csdn.net/xaiobit_hl/article/details/128025295

        因为构造方法的执行时机太靠前了,如果依赖注入 Bean 的方式是构造方法注入,就会导致 A 需要 B 的时候,B 压根还没执行到属性赋值那一步,而 B 需要 A 的时候,A 也压根没执行到属性赋值这一步,这就是先有鸡还是先有蛋的问题了。(A 依赖 B 对象的时候,需要 DI 注入 B 对象到当前对象中,这一步在 Bean 的生命周期中也叫做属性赋值,而需要给 B 进行赋值,B 对象就得先执行完实例化、属性赋值、初始化这三个生命周期)

2. Spring 三级缓存如何解决循环依赖问题 ?

就拿两个对象间的循环依赖来举例:

首先单例对象一定需要存储下来,所以需要一个一级缓存来存储完全初始化好的对象:

A 和  B 相互依赖时, Bean 的执行流程:

A 和 B 相互依赖的执行流程(不牵扯 AOP):

  1. 实例化 A 对象;
  2. 对 A 的依赖对象 B 进行属性赋值,发现 B 对象还没有初始化好,就需要先初始化 B 对象;
  3. B  对象实例化;
  4. 对 B 的依赖对象 A 进行属性赋值,此时 A 还是一个半成品,还没有初始化好,于是 A 对象的引用地址赋值给 B 中的依赖对象;
  5. B 对象进行初始化,B 对象初始化完之后,A 对象也就属性赋值完毕;
  6. A 对象执行初始化了。

        上述流程中,第 4  步,B 对象在给 A 对象进行属性赋值的时候,此时的 A 对象还是个半成品,那么 B 在给 A 属性进行属性赋值的时候,这个半成品也是需要进行存起来的,否则 B 对象后续的流程就无法执行下去了,这时候就有了二级缓存,二级缓存就是用来存放没有初始化好的对象。

        本来有了一级缓存,二级缓存就可以解决循环依赖问题了,但是第三者 AOP 的出现,就导致故事变得复杂起来了。

【前置铺垫】程序中如果使用了 AOP(动态代理),那么 Spring 中存储的就是代理对象,而不是目标对象了,那么在构建代理对象的时候,一定是需要目标对象的,所以代理对象既不能存储在一级缓存,也不能存储在二级缓存,但是这个代理对象是一定要存储下来的,否则就变成多例的了,这就违背了解决循环依赖的前置条件 1,所以这时候就引入了三级缓存。(代理对象中存放的是工厂对象 FactoryBean,代理对象就是通过工厂对象生成的)

这三个缓存在源码中其实就是三个 map :

  1. singletonObjects:一级缓存(ConcurrentHashMap)
  2. singletonFactories:三级缓存(HashMap)
  3. earlySingletonObjects:二级缓存(ConcurrentHashMap)

为什么三级缓存使用 HashMap:(doCreateBean())

因为它里面放的是一个 lambda 表达式,它不是一个真正的对象,所以它就不怕线程安全问题。

所以 Spring 里面解决循环依赖的问题,就是引入了三级缓存来解决的。

程序中如果使用到了 AOP,那么前面 A 依赖 B,B 依赖 A 的执行流程就需要稍作改变:

         

        上述流程执行完毕后,一级缓存中就有了 A 对象和 B 对象了,其他对象需要依赖这俩对象时,就可以从一级缓存中去取了。 

上述流程 4 中,B 进行属性赋值时,寻找 A 对象的流程源码如下:

B 对象执行属性赋值时,寻找 A 对象流程:

        先从一级缓存中找,没找到,就去二级缓存找,也没找到,就去三级缓存找,此时就会执行 A 的 lambda 表达式,此时 A 对象不管是代理对象还是目标对象,都会被晋级到二级缓存中(考虑性能),当其他对象中依赖 A 对象时,就不再需要从三级缓存中拿了,而是直接从二级缓存中取;

        虽然循环依赖问题确实存在,也不可避免,但是 SpringBoot 3.0 以及 Spring framework 6.0 之后,默认情况下就关闭了对于循环依赖的一个支持了,也就是说在 Spring 高版本底下,如果存在循环依赖的问题,Spring 就会告诉你,你的项目中有循环依赖,项目启动失败。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Master_hl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值