今天面试了同程旅行,面试官问到了这个问题,所以今天来总结学习一下 Spring是如何解决循环依赖问题?
前言
Spring的依赖注入分为 setter注入和 构造器注入
这里说的解决循环依赖主要指的是:单例模式下的setter循环依赖
如果是:构造器的循环依赖,非单例循环依赖。 Spring都是解决不了的
Spring的生命周期
讲循环依赖的问题,需要先说说Spring生命周期这个问题,先看看在Spring中,一个Bean从创建到销毁经历过哪些过程?
Spring中的bean的生命周期主要包含四个阶段:实例化Bean --> Bean属性填充 --> 初始化Bean -->销毁Bean
什么是循环依赖
我们定义这样两个类A、B,A中有一个属性引用的B,B中有一个属性是引用的A
参考上面Bean的生命周期,就能得出:在实例化A的时候,去赋值属性B,就去找B,B这个时候又需要实例化赋值属性A的时候,又去找A。接着A又找B,就形成了循环依赖。
@Component
public class A{
@Autowired
B b;
}
@Component
public class B{
@Autowired
A a;
}
Spring的三级缓存
// 1级缓存:存放完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 2级缓存:存放实例化(尚未填充属性)+代理(如果有代理)后的单例bean
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
//3级缓存,存放bean工厂对象,用于解决循环依赖
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
假设只有一级缓存
如果A在实例化过后,不进行属性值的填充,就把它丢到一级缓存里面行吗?其实这样缓存里面的A也不是一个真正的A,它缺胳膊少腿,用它的时候回报一个空指针异常,而且我们的一级缓存规定是完全初始化好的bean才放在一级缓存立面,给我们的程序后续进行使用
假设只有二级缓存
我们把一级缓存叫 Map1,二级缓存叫Map2
首先还是从实例化A开始,我们对A实例化过后,还没有进行属性填充的时候,就把A对象的引用放在Map2备用,然后进行属性填充,A去填充B的时候,发现B没有实例化;于是等B同样实例化过后,B也把自己的引用放在Map2中,B开始进行属性填充,发现Map1中没有A,但在Map2中找到了A,是自己装填完整。这个时候B把完整的自己放在了Map1,随手把Map2中的半成品删除。
再回到A的阶段,A中发现Map1中有了B,那么A也可以装填完整,于是最终A、B都完成了自己的创建。
文字有点儿扭成一坨不容易看懂,我画个流程图,大家凑合看
按照以上所述,二级缓存已经完美解决了循环依赖的问题,为什么Spring还需要引入三级缓存来做呢?
主要原因是因为:Spring的Aop产生的代理对象
Srping的代理对象产生阶段是在填充属性后才进行,原理是通过后置处理器BeanPostProcssor来实现
如果 Spring 选择二级缓存来解决循环依赖的话,那么就意味着所有 Bean 都需要在实例化完成之后就立马为其创建代理
所以Spring选择使用三级缓存,因为循环依赖的出现,导致了 Spring 不得不提前去创建代理,因为如果不提前创建代理对象,那么注入的就是原始对象,这样就会产生错误。
Spring的三级缓存
首先我们还是进行实例化A对象,我们将A的ObjectFactory对象放入Map3中,同样进行属性填充,这个时候发现需要属性B,这个时候B还没有创建。
接着去创建B,实例化B的过程中,一样的我们先将B的ObjectFactory放到Map3中,继续执行B的属性填充,去寻找A对象。此时Map1和Map2中都没有找到A对象,但在Map3中发现了有A的 ObjectFactory对象,那么我们就可以通过这个 ObjectFactory对象获取到A的早期对象,并且把A早期对象放到Map2中国,同时删除Map3中的A,我们把Map2中的早期对象A给了B,让B对象进行属性装填,接着B完整了,就把B放入到一级缓存当中,再把Map3中B的 ObjectFactory删除。
B创建完成,A继续执行b属性的填充就可以拿到Map1中拿到B对象,这样A也完整了。
最后把A对象放入到Map1单重,删除Map2中的A,于是我们的循环依赖的解决了
画一张流程图试着理解一下: