Spring中存在三级缓存:
- 第一层缓存(singletonObjects):单例对象缓存池,已经实例化并且属性赋值,这里的对象是成熟对象;
- 第二层缓存(earlySingletonObjects):单例对象缓存池,已经实例化但尚未set属性赋值(Bean的生命周期第二步),这里的对象是半成品对象;
- 第三层缓存(singletonFactories): 单例工厂的缓存,里面放的是工厂对象(实际上就是一串lambda),每个工厂可以创建对应的Bean
只有单例才会搞三级缓存这套东西,多例的话每次getBean都直接重新实例化一个!
Spring生成Bean的流程:
假使 A 和 B 发生了循环依赖:
- 先往createingSet(一个Set集合,记录正在创建的Bean,用于后续判断是否出现循环依赖)放入A
- 一级缓存中没找到A,通过无参构造实例化A,将和A有关的一串lambda表达式放入三级缓存(此时A的实例化和属性注入就已经分离开了),set属性赋值注入B
- 通过createingSet发现没有B,知道此时还没出现循环依赖,那就去一级缓存找B,找不到,往createingSet放入B,通过无参构造实例化B,将和B有关的一串lambda表达式放入三级缓存,进行set属性注入A
- 通过createingSet发现有A,就知道发生了循环依赖!那就去二级缓存(不是一级缓存了,因为发生了循环依赖,逻辑需要有变化)找,没找到,去三级缓存找,找到有关A的lambda表达式,执行lambda:
-
- 如果A需要被AOP,则此时提前对其AOP(正常Bean的生命周期下应该是在初始化后AOP),将AOP后的代理对象放入二级缓存,并从三级缓存中删除(这样能保证AOP后的代理对象是单例的,不会被多次生成,因为lambda相当于一次性的消耗品了,同时也会上锁保证线程安全),并将代理对象放入earlyProxyReferences(用于存储有哪些对象提前AOP了,后续有用)
- 如果A不需要被AOP,则生成A的普通对象并放入二级缓存,并从三级缓存中删除
- 此时B拿到了对象A(可能是代理的,也可能是普通的)进行set注入,进行B的其它Bean生命周期,当Bean成熟之后放入一级缓存,移除二、三级缓存(实际上此时二级缓存中没有B,三级缓存中有B)
- 此时返回到第2步,A就拿到了B的成熟对象,set注入到A中,然后进行A的其它Bean生命周期
- A的其它生命周期包括AOP(Spring中AOP是在Bean后置处理器的after方法中),但这时A可能已经在lambda中被提前AOP了,这就需要通过之前的earlyProxyReferences判断A有没有提前AOP,防止重复AOP
- 将成熟的A放入一级缓存,移除二、三级缓存(实际上此时三级缓存中没有A,二级缓存中有A)
如果 A 和 B 发生循环依赖的同时,A 又和 C 发生了循环依赖:
在A中注入B之后,要注入C,C从createingSet中发现有A,就知道发生了循环依赖,那就去二级缓存找,这时候能找得到,因为已经在B进行初始化的时候从三级缓存中生成了一个A并放入到了二级缓存,那么就直接拿这个二级缓存中的A注入,然后走C的其它Bean生命周期,将C放入一级缓存
各级缓存的作用:
- 一级缓存:总得有个单例池用来保存你所有成熟的Bean吧?不然你BeanFactory从哪获取Bean?这个单例池就是一级缓存
- 二级缓存:用来保证Bean是单例的。因为你三级缓存中的lambda应该是一次性的消耗品,以此保证只会生成一个Bean,那么这个生成的半成品Bean就先放入二级缓存中缓存起来。用于存放提前曝光的不成熟的Bean
- 三级缓存:用来打破循环并解决出现AOP的情况下的循环依赖。当发生了循环依赖,我们可以保证你能从三级缓存中得到Bean(因为单例Bean不管怎样都会先往三级缓存中存个lambda),通过三级缓存中的lambda,我们可以生成半成品的普通Bean或者代理Bean,再放入二级缓存,并从三级缓存中删除,这就意味着三级缓存中的lambda是一次性的消耗品,这样就可以保证Bean的单例
一级缓存能解决循环依赖吗?
将半成品和成品都放入一级缓存,这样也能解决普通的循环依赖,但可能造成一些空指针异常,毕竟里面有半成品;而且混杂成品和半成品在一起,不太优雅;对于出现AOP的循环依赖无法解决。
可不可以使用一、二级缓存解决循环依赖?
可以,但这样如果出现AOP的话,在对象实例化时就要完成AOP代理,生成代理对象,再进行属性注入,即先创建代理对象,再对代理对象进行属性注入,这与Spring设计理念中“将Bean的初始化和代理解耦”是不符的
那么就可以通过三级缓存中的lambda判断,只有当出现AOP + 循环依赖的时候,才提前创建代理对象,然后进行依赖注入初始化,否则创建普通对象,走正常的Bean生命周期
可不可以使用一、三级缓存解决循环依赖?
不可以,因为使用到三级缓存这套东西就说明Bean是单例的,如果用三级缓存,那么每次使用三级缓存的lambda生成的半成品Bean都不是同一个,当出现A与B发生循环依赖,A与C也发生循环依赖的这种情况,B中的A和C中的A就不是同一个了!必须要让三级缓存中的lambda变为一次性的,并且有个地方存储这个一次性生成出来的半成品,这个地方就是二级缓存!
Spring如何解决循环依赖?
createingSet(判断是否出现循环依赖) + 三级缓存(解决循环依赖) + earlyProxyReferences(防止解决循环依赖的过程中重复AOP)
哪些循环依赖默认情况下无法解决?
- 构造注入下的循环依赖是无法解决的,因为在实例化的时候就需要强制注入
- 两个都是多例对象。因为多例不会放入缓存,多实例Bean是每次调用一次getBean都会执行一次构造方法并且给属性赋值,根本没有三级缓存,因此不能解决循环依赖。
- 一个单例,一个多例。如果先实例化多例,解决不了。如果先实例化单例,可以解决
其它循环依赖如何解决:
@Lazy注解:
@Lazy可以出现在类上、方法上、构造器上、方法参数上、成员变量上。当在方法上时,通常与@Bean一起使用
@Lazy注解可以解决构造器注入情况下的循环依赖。当A的构造方法上出现@Lazy,要注入B时,Spring会生成一个B的代理对象,这个代理对象和B没什么两样,继承于B,同时聚合了一个B对象(原始B),这时A就可以直接实例化,然后注入到原始的B中
这时,A.getB不等于原始B,因为A中的B是代理B
但是A.getB.getA却是最开始的A,代理B中的getA方法其实是调用了代理B中聚合的原始B中的getA方法,同样,代理B的其它方法也是调用了原始B的其它方法
所以当A要用B中的方法时,虽然不是直接调用原始B的方法,但其实通过代理B还是间接调用到了原始B中的方法,当代理B要调用A中的方法时,会先通过聚合的原始B获取到A,然后调用A中的方法。
综上,多出一个代理B其实没有任何影响,但是这种方式可以直接让A实例化,直接就避免出现了循环依赖
同时,被@Lazy标注的Bean是懒加载的,正常情况下Bean在Spring容器启动后就会全部创建,但被@Lazy标注的Bean只有在被使用到时才会创建