前言
循环依赖是近年比较火的面试题,正所谓温故而知新,每次看都新!所以,强烈推荐只看5.0.X版本的源码,目前高版本的需要使用jdk11有阅读障碍且要安装对应jdk才能编译;其内容大同小异。
循环依赖是什么
A -> B -> A 互相引用形成闭环即为循环依赖,图就不画了。
循环依赖的分类
1.setter注入循环: spring只能解决单例bean setter依赖注入造成的循环依赖;
2.多例模式循环:多例产生的循环依赖无法解决 (每次调用doGetBean都产生一个新的Bean,要么堆溢出要么栈溢出 因此直接Spring抛异常 BeanCurrentlyInCreationException)
3.构造器注入循环:构造方法进行依赖注入时产生的循环依赖无法解决 (把锅甩给java, new A()时构造方法去new B() 无穷无尽)
解决单例Bean循环依赖关键思想:实例化和初始化分开执行
Bean的生命周期核心方法如下图:
认识三个缓存
//一级缓存 存储完整的单例bean
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//三级缓存 存储Bean工厂
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//二级缓存 存储半成品仅完成实例化的Bean对象
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
每个缓存存在的意义是什么?
singletonFactories : 一级缓存用于存储完整的单例Bean,常被我们口语化成称为IOC容器(Bean容器)
earlySingletonObjects :二级缓存用于存储已被实例化但未完成初始化的提前曝光对象,它为了延迟Aop代理类的代理过程,当未发生循环依赖时遵循spring最初的设计原则完成初始化后进行增强代理。同时能起到优化作用(因三级缓存需要遍历BeanPostProcess增强器
singletonFactories : 三级缓存储存的是能创建增强后的完整的Bean对象,不管是否发生循环依赖都需要加入进来,因为spring也不知道此类是否会发生循环依赖
结合上图分析:
当发生A -> B -> A 时: doGetBean(A)->....->populateBean->doGetBean(B)->....->populateBean->doGetBean(A)
populateBean时发生循环依赖时,会递归调用doGetBean,并提前暴露A放入二级缓存中 使B可以完成属性注入,因为A的生命周期还未走完 所以A只是个半成品,B完成属性注入之后继续走A的初始化流程;
三个缓存结合使用
//正常第一次调用 返回null
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//一级缓存获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//单例双重检查机制
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//执行三级缓存工厂方法
singletonObject = singletonFactory.getObject();
//放入二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
- Spring 从一级缓存
singletonObjects获取bean - 如果获取不到并且对象正在创建,就会尝试从二级缓存
earlySingletonObjects获取 - 如果获取不到则从三级缓存
singletonFactories中创建并放入二级缓存
为什么需要三个缓存?
其实二级缓存也能解决循环依赖,第一感觉三级缓存存放的工厂只使用一次完全没必要存放,只需要直接执行内部方法放到二级缓存就行了这是我最初的想法。但是这么做的话意味着所有的Aop代理类在初始化时都需要立即代理完成。这违背了spring的设计初衷。如果砍掉二级缓存的话,一个类可能被多次引用,在初始化过程多次创建,在创建过程中会遍历beanPostProcess增强器 严重拖慢启动时间。
总结
当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。
当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取:
第一步,先获取到三级缓存中的工厂;
第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。
当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!