Spring 循环依赖、三级缓存原理详解

1. Bean 的生命周期

拿 AService(单例模式) 举例,首先我们先看一下 AService 的 Bean 的生命周期:
1)Bean的实例化,拿到 AService 的构造方法,得到AService 的普通对象
2)对 AService 进行依赖注入,例如 AService 中有 BService 的属性,那么就会去填充BService属性(即给BService 赋值)
3)填充其他属性
4)做一些其他的事情(初始化、AOP)
5)添加到单例池

2. 循环依赖

在上述第二步中,我们要填充 BService 属性, 就会去单例池中找,看有没有 BService 的Bean,如果没有的话就会去创建 BService,如果BService 中有 AService 属性,这时候会出现循环依赖的现象

3. 三级缓存

先介绍一下三级缓存的三个Map:
第一级缓存:singletonObjects 就是单例池
第二级缓存:earlySingletonObjects
第三级缓存:singletonFactories

解决循环依赖的关键在于如何打破循环依赖,比如说我们可以引入一个Map,将第一步产生的AService 的普通对象放到这个Map中,这样在 填充BService 的时候就可以从这个Map中拿到AService 的对象;

但是这样就解决问题了吗?并不是,在填充完AService 的属性后,AService 还要做一些其他事情,例如初始化、AOP, 在进行AOP的时候会产生 AService 的代理对象,我们最终应该要把代理对象放到单例池中, 其他对象依赖AService,也应该是AService 的代理对象,而在上述解决方案中,我们给 BService 赋值的是 AServce 的普通对象;

那么我们将AOP提前,在产生 AService 的普通对象之后就进行AOP,将AService 的代理对象放到 Map 中是不是就可以解决问题?

4. 提前进行AOP

按照上述方案来讲,一个Bean 没有出现循环依赖的话,那么应该在第四步进行AOP, 如果出现了循环依赖,那么就需要将AOP提前,在第一步之后就进行AOP;

但是问题又来了,我们如何在第一步就知道这个Bean 出现了循环依赖呢?在填充 Bservice 属性时发现需要 AService,这个时候我们才知道出现了循环依赖,如何解决这个问题?

其实我们只需要在一开始创建一个 creatingSet, 将正在创建的Bean 放在Set 中,填充BService 时发现 AService 正在创建中,这样就可以判断AService 出现了循环依赖,判断出现循环依赖之后,我们再去对AService进行AOP,产生AService 的代理对象

0. creatingSet<AService>
1. AService实例化 --> AService普通对象
2. 填充BService -->单例池中没有BService -->创建BService
    BServiceBean 生命周期:
    2.1 实例化 --> 普通对象
    2.2 填充aService --> 单例池Map中没有 AService -->creatingSet 中有-->AService 出现了循环依赖 --> AOP --> AService 代理对象
3. 填充其他属性
4. 做一些其他的事情(初始化、AOP),如果提前进行了AOP,这一步就不用做AOP5. 添加到单例池
6. creatingSet.remove<AService>

那我们什么时候将AService 的代理对象添加到单例池中?创建完代理对象就放入单例池中可以吗?在创建AService 的代理对象时,是需要AService 的普通对象的,因为AService代理对象的 target 是指向AService 的普通对象的,但是这时候 普通对象的属性是空的,甚至还没有经过初始化等一些其他步骤,所以我们不能创建完代理对象就将他放到单例池中。

5. 二级缓存 earlySingletonObjects 的作用

现在我们不考虑什么时候将代理对象放到单例池的问题,来看看另一个问题:如果有另一个CService ,它也依赖于AService,那么在填充CService 的AService 属性时,也会判断出AService 出现了循环依赖,也会去进行AOP产生AService 的代理对象,这样就有问题了,单例Bean产生了多个代理对象,怎么解决这个问题?

其实很好解决,再增加一个Map,这个Map 就是我们所说的二级缓存 earlySingletonObjects ,这样整个流程就变成了:

填充AService属性 --->单例池Map 中没有找到AService --->creatingSet中有AService--->判断AService出现了循环依赖 --->  earlySingletonObjects 没有找到 AService 的代理对象 --->AOP --->AService 代理对象 ---> 存到二级缓存里

由此来看,二级缓存的作用还是保持单例,它存的是没有经过完整生命周期的对象,这些对象是出现了循环依赖后,提前创建的Bean 对象,这也是为什么叫 earlySingletonObjects 的原因

这样什么时候将代理对象放到单例池的问题也解决了,将代理对象存放到二级缓存后, 我们填充普通对象的其他属性,由于代理对象的target 指向普通对象,相当于代理对象的属性也填充完毕,然后从二级缓存中拿到代理对象,加到单例池中。

6. 第三级缓存 singletonFactories 的作用

经过上面描述,大家可能觉得只用两个Map就可以解决循环依赖了,那么第三级缓存是干什么的?我们之前说过解决循环依赖的关键在于打破循环,而目前的二级缓存并没有打破循环(在填充 AService 的属性BService 时,需要对AService进行AOP 生成代理对象,而代理对象又需要普通对象,整个循环还是没有打破),所以我们需要第三级缓存去打破这个循环,将普通对象放到第三级缓存中(即在第3节中提到的Map);但真正的Spring 源码里,第三级缓存里存的是 ObjectFactor,它是一个函数式接口(lambda表达式),即:

singletonFactories.put('AService', ()->getEarlyBeanReference(beanName, mbd, AService普通对象))

现在填充 BService 中 AService 属性的流程就变成了:

填充AService ---> 单例池Map ---> creatingSet ---> AService 出现了循环依赖 ---> earlySingletonObjects中没有 --->从 singletonFactories 拿到对象工厂 --->lambda ---> 执行lambda --->AOP --->ASerivce 代理对象 ---> 放到 earlySingletonObjects中 ---> 将三级缓存移除掉(保持单例)

现在我们可以总结一个三级缓存的作用:
第一级缓存:singletonObjects 就是单例池,保存经过完整生命周期的Bean对象;
第二级缓存:earlySingletonObjects ,保存的是出现了循环依赖后,提前产生的代理对象,让代理对象也保持单例; 如果出现了循环依赖,但是不需要AOP,那么得到的就是普通对象,放到二级缓存的也是普通对象,总而言之,二级缓存保存的是没有经过完整生命周期的 Bean 对象;
第三级缓存:singletonFactories,保存的是对象工厂,作用是打破循环;

有一个小点:出现循环依赖提前进行AOP 之后,后续怎么知道不需要AOP了呢?在正常执行AOP 的逻辑里,会判断从二级缓存拿到的对象是不是为空,如果不为空,那么说明提前进行了AOP,不需要AOP了

7. @Lazy

如果在构造方法中出现循环依赖,这时候连普通对象都创建不出来,这种情况是Spring 解决不了的,这时候可以加 @Lazy 解决;

在AService的属性BService加 @Lazy 注解, 会直接创建一个 BService 的代理对象, 这里的代理对象和 AOP 的代理对象不同,这个代理对象没有什么依赖,可以直接创建出来,在调用 AService 的方法真正用到 BService的时候,才会真正创建 BService 的Bean。

  • 27
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值