【面试Spring必备】Spring解决循环依赖问题--超详细分析

Spring如何解决循环依赖问题?

代码模拟
@Component
class A{
    
    @Autowired
	private B b;
}

@Component
class B{
    
    @Autowired
	private A a;
}
图示

image-20210916182817909

结合Bean的生命周期分析

img

详细的bean生命周期可参考

  1. Spring扫描class得到BeanDefinition
  2. 根据bean的definition生成bean
  3. 推断构造方法(优先选无参)
  4. 根据构造方法通过反射创建对象(放入三级缓存lamda表达式后面详细讨论)
  5. 填充对象属性(依赖注入)
  6. 如果对象的某个方法被AOP了,那么则需要根据原对象生成代理对象
  7. 把最终生成的对象放进单例池中

在Spring中,bean的生命周期导致循环依赖问题

image-20210916182626831

问题解决的关键

在Spring中,如果通过Set方式注入属性,【有参构造无法提前暴露对象,

不能解决循环依赖问题】那么对象的实例化和初始化就是分开的,实例化

完成的对象是不完整的对象,但是却可以之间给其他对象引用的,所以可

以在此增加一层缓存,把完成实例化但未初始化的对象提前暴露出去,让

其他对象能够进行引用,就解开了上图中的闭环问题。

image-20210920152913439

解决方案【三级缓存】

  • 一级缓存:singletonObjects 拥有完整生命周期的bean对象
  • 二级缓存:earlySingletonObjects 半成品对象,如果提前进行了AOP,存放的就是半成品代理对象
  • 三级缓存:singletonFactories 对象工厂,一个lamda表达式,用于回调是否生成代理对象还是原始对象

image-20210916203955151

详细步骤

yu

第1步,反射构造A对象的时候将对象放入三级缓存,存入的是一个未执行的lambda表达式,

之后填充属性需要B,就会从三级缓存找(没有找到)------>二级缓存(没有找到)------>一级缓存(没有找到)

最终都没有找到就去创建B,开始了B的生命周期,同样B填充属性需要依赖A,就会找三级缓存(找到)

执行三级缓存中A的lambda表达式获取一个对象。

并检测如果需要进行AOP就返回一个代理对象【不完整】,反之就返回一个普通对象。

之后lambda表达式执行完三级缓存删除,放入二级缓存【没有经过完整生命周期的对象A(或代理对象A)的引用。

最后当B的生命周期结束之后返回A的生命周期,A填充属性B,判断是否已经提前进行AOP并决定是否

需要进行AOP,发现二级缓存中含有A对象(或代理对象A)就将二级缓存删除,放入一级缓存中。

如下图所示:

image-20210917180031294

为什么需要三级缓存?

只要两个缓存确实可以做到解决循环依赖的问题,但是有一个前提这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用两个缓存是无法解决问题,下面来看一下bean被AOP进行了切面代理的场景

三级缓存实现aop(代理对象实例化的时候,实例化对象是原始对象,若没有三级缓存,此时若根据

类名直接获取对象的话,获取的是原始对象,而我们想要的肯定是通过类名直接获取代理对象,所以

Spring在类加载过程中,直接将实例化的对象放入三级缓存中,从三级缓存中获取类对象的时候,判

断类是否被代理,若被代理则返回代理对象)

一个场景:

a对象依赖b,c对象,同时b,c依赖a对象;那么我们想一下在b对象注入a对象时没有在一级缓存里找到我

们的a的bean对象也没有在二级缓存找到我们的代理对象a,此时spring会在三级缓存找到存放着我们

的原始对象a的lambda表达式,spring执行该lambda表达式生成的代理对象a,此时b注入a完成其他步

骤正常执行,这里注意我们并没有一个完整的a的bean对象在我们的SingletonObjects里面,所以我们

的c对象在注入a对象时又会生成一个代理对象a,这个时候问题来了,代理对象a出现不一致的情况,

那么如何解决呢?这时候二级缓存的重要性体现出来了,我们将这个代理对象放earlySingletonObjects

二级缓存,key存放我们的beanName,value存放我们的代理对象。

现在我们再来走一遍这个场景:

a对象实例化好,在进行注入时发现依赖a,c两个对象,此时spring会去先创建b,c对象,b对象会去注入

a对象,会先去SingletonObjects一级缓存和earlySingletonObjects二级缓存找,发现没找到就去

SingletonFactories三级缓存,找到带有原始对象a的lambda表达式并执行该lambda表达式获得一个代

理对象a并放入二级缓存将该lambda在三级缓存删除,b对象的流程正常执行,此时c对象也需要注入

一个a对象,先去一级缓存找,没找到,再去二级缓存找找到了代理对象a,此时c对象也正常执行现

在a对象也能成功完成注入,spring的循环依赖已经解决。

总结:

  • 只有一级三级缓存,加入二级缓存可避免lambda表达式重复生成代理对象,保证单例的Spring的bean。
  • 如果只有一级二级缓存,三级缓存通过lambda表达式可灵活决定是否是需要提前进行aop并生成代理对象。
  • 如果没有循环依赖,三级缓存的lambda表达式就不会执行,二级缓存也不会使用,不影响Spring的正常bean生命周期。
  • 循环依赖两个核心
    • 单例bean
    • Set方式注入属性
  • 具体实现细节可参考Spring源码

在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Steve_hanhaiLong

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

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

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

打赏作者

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

抵扣说明:

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

余额充值