什么是循环依赖
循坏依赖就是字面意思,A 依赖了 B,B 同时也依赖了 A。
如下所示
@Component
public class A {
// A中注入了B
@Autowired
private B b;
}
@Component
public class B {
// B中也注入了A
@Autowired
private A a;
又或者是下面这种
// 自己依赖自己
@Component
public class A {
// A中注入了A
@Autowired
private A a;
}
解决循环依赖
前提
解决循环依赖有两个前置条件:
- 出现循环依赖的 Bean 必须是单例。
- 依赖注入的方式不全是构造器方式注入。
解决
基于上面的问题,我们知道Bean的生命周期,本质上解决循环依赖的问题就是三级缓存,通过三级缓存提前拿到未初始化完全的对象。
第一级缓存:用来保存实例化、初始化都完成的对象。
第二级缓存:用来保存实例化完成,但是未初始化完成的对象。
第三级缓存:用来保存一个对象工厂,提供一个匿名内部类,用于创建二级缓存中的对象。
A 对象创建过程:
1、创建对象 A,实例化的时候把 A 对象工厂放入三级缓存
2、A 注入属性时,发现依赖 B,转而去实例化 B。
3、同样创建对象 B,注入属性时发现依赖 A,一次从一级到三级缓存查询 A, 从三级缓存通过对象工厂拿到 A,把 A 放入二级缓存,同时删除三级缓存中的 A,此时,B 已经实例化完并且初始化完成,把 B 放入一级缓存。
4、接着继续创建 A,顺利从一级缓存拿到实例化且初始化完成的 B 对象,A 对象创建也完成,删除二级缓存中的 A,同时把A 放入一级缓存。
5、最后,一级缓存中保存着实例化、初始化都完成的 A、B 对象。
因此,由于把实例化和初始化的流程分开了,所以如果都是用构造器的话,就没法分离这个操作,所以都是构造器的话就无法解决循环依赖的问题了。
为什么要三级缓存?二级不行吗?
不可以,主要是为了生成代理对象。
因为三级缓存中放的是生成具体对象的匿名内部类,他可以生成代理对象,也可以是普通的实例对象。
使用三级缓存主要是为了保证不管什么时候使用的都是一个对象。
假设只有二级缓存的情况,往二级缓存中放的显示一个普通的Bean对象,BeanPostProcessor去生成代理对象之后,覆盖掉二级缓存中的普通Bean对象,那么多线程环境下可能取到的对象就不一致了。