循环依赖其实就是循环引用, 简单来说, 就是两个或两个以上的bean互相持有对象, 形成闭环. 例如A依赖于B, B依赖于C, 而C又依赖于A
Spring单例对象的初始化主要分为三个步骤:
循环依赖主要发生在第一、第二步骤, 也就是构造器循环依赖和属性循环依赖
Spring解决循环依赖也是从bean的初始化过程着手的, 对于单例来说, 在Spring容器整个生命周期内, 有且只有一个对象, 所以很容易想到这个对象应该存在Cache中, Spring为了解决单例的循环依赖, 使用了三级缓存
所谓三级缓存, 其实就是存放不同状态下的bean
下面的代码就是我们获取bean的逻辑
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解决循环依赖的诀窍是依赖于singletonFactories这个三级缓存
三级缓存的类型是ObjectFactory, 它是一个泛型接口, 只有一个方法
AbstractAutowireCapableBeanFactory#doCreateBean 我们一起来看下源码
如果bean是单例, 同时允许从singletonFactories获取bean,并且当前bean正在创建中, ,那么就把beanName放入三级缓存(singletonFactories)中:
这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。
知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。
Spring通过三级缓存来解决循环依赖, 其中一级缓存为单例池(singletonObjects), 二级缓存为早期曝光对象(earlySingletonObjects), 三级缓存为早期曝光工厂(singletonFactories)
当A、B两个类发生循环依赖时, 在A完成实例化之后, 就使用实例化后的对象去创建一个对象工厂, 并添加到三级缓存中, 如果A被AOP代理, 那么通过这个工厂获取到的就是A代理后的对象, 反之, 则为A实例化对象
当A进行属性注入时, 就会创建B, 此时B又依赖于A, 所以创建B的同时又会调用getBean(A)来获取A的依赖, 会依次从一级、二级、三级缓存中去寻找A, 最终在三级缓存中找到了A的工厂, 并生成对应的对象, 将其注入到B中, 紧接着B会走完它的生命流程, 包括初始化、后置处理等等, 当B创建完成后, B的对象就会存入一级缓存, 此时就会回到一开始的地方, 将B再注入到A中, 此后A再走完它的生命周期
为什么不能只使用二级缓存?
如果只使用二级缓存, 那么二级缓存是在A实例化后就缓存好了, 这个时候A在设置属性的过程中去获取B(这个时候A还没有被aop的后置处理器增强),创建B的过程中,B依赖A,b去缓存中拿A拿到的是没有经过AOP代理的A。就有问题。
简单来说: 如果只使用二级缓存无法解决AOP代理问题, 因为二级缓存的执行时机是实例化之后, 属性注入之前, 而AOP是在后置处理器阶段执行(在初始化前后), 此时二级缓存无法确定需要缓存的是实例对象还是代理对象
二级缓存的作用是什么?
如果出现循环依赖+aop时,多个地方注入这个动态代理对象需要保证都是同一个对象,而三级缓存中的取出来的动态代理对象每次都是新对象,地址值不一样。
参考内容:
spring如何解决循环依赖
spring为什么要使用三级缓存解决循环依赖
spring三级缓存之为大多数合理而设计