什么是 Spring 的三级缓存?它是如何解决循环依赖的?
熊猫
Spring 的三级缓存是解决循环依赖问题的关键机制。循环依赖是指,当创建对象A需要依赖于B,创建B对象需要依赖于A,这种情况就是循环依赖。 Spirng为了解决这种循环依赖,就是采用了三级缓存,三级缓存分别是:一级缓存用来存放完全初始化好的Bean,二级缓存用来存放半成品Bean,也就是没有完全初始化的Bean,三级缓存存储Bean的代理工厂。
Spring三级缓存的结构:
1. 一级缓存(singletonObjects): 存放完全初始化好的单例 Bean Map<String, Object>结构 Bean 创建完成后放入此缓存
2.二级缓存(earlySingletonObjects): 存放早期曝光的 Bean,尚未完全初始化 Map<String, Object>结构 主要用于解决循环依赖
3.三级缓存(singletonFactories): 存放 Bean 的工厂对象 Map<String, ObjectFactory<?>>结构 主要用于处理 AOP 代理
Spring三级缓存解决循环依赖的过程
当Spring尝试创建Bean A时,首先实例化A对象(就是new出来的对象,还没有进行填充熟悉),然后把A的创建工厂放入三级缓存,接着开始填充A的熟属性。 当填充A属性的时候遇到需要依赖Bean B,就会去创建Bean B,同样也会实例化B,把B的创建工厂放入三级缓存,然后填充B的属性,后面发现B依赖与A,则就会从 一级缓存开始找,发现一级没有,找二级,二级也没有,找三级,发现三级有A的创建工厂,Spring通过这个创建工厂获取到了A的早期实例,并且将实例放入到二级缓存,将三级缓存中的A工厂删除。 B注入了A的早期工厂依赖后,完成了初始化,然后将存入到一级缓存中。然后回到A的创建流程,继续填充属性,这时候会从一级缓存中获取到B的完整实例,完成A的初始化,最后A也会放入到一级缓存。 这样,循环依赖问题就解决了。
为什么不直接使用两个缓存?
假设去掉三级缓存,只保留一级缓存和二级缓存
假设存在依赖关系:A → B(A 依赖 B)、B → A(B 依赖 A),且 A 配置了 AOP(如事务、日志),需要生成代理对象。
A 开始实例化(new A()),但未初始化(未注入 B),此时将原始 A 对象放入二级缓存;
A 尝试注入 B,发现 B 未创建,转而开始创建 B;
B 实例化后,尝试注入 A,从二级缓存中获取到原始 A 对象并注入;
B 完成初始化,成为 “成品 Bean” 放入一级缓存,回到 A 的创建流程;
A 继续初始化,此时需要为 A 生成 AOP 代理对象(因为配置了 AOP);
最终 A 的代理对象成为 “成品 Bean” 放入一级缓存,但 B 中已经注入了原始 A 对象,导致 B 依赖的 A 和最终一级缓存的 A 不是同一个对象,出现 “代理对象不一致” 问题。
本质问题:二级缓存只能提前暴露 “固定对象”(要么原始 Bean,要么代理 Bean),但 A 的代理对象需要在 A 自身初始化后期才能生成(依赖 A 的属性、切面配置等),无法在实例化时就确定,导致提前暴露的原始 Bean 和最终的代理 Bean 脱节。
什么情况下三级缓存解决不了循环依赖
Spring只能解决setter方式注入的循环依赖,对于以下情况是无法解决的: setter方式注入依赖,实例化和初始化可以进行分开,new的时候只要满足构造器的初始化即可,而其它属性可以通过setter初始化的,可以进行实例化后再进行初始化。
- 构造器注入的循环依赖(因为实例化和初始化是一体的,没法提前曝光)
- prototype作用域的Bean循环依赖(因为 prototype 的 Bean 不会放入缓存)
- 使用@DependOn导致的循环依赖 DependOn注解是用来指定Bean的初始化顺序