一、简介
循环依赖就是循环引用,就是两个或多个 bean 互相之间持有对方。比如 beanA 引用 beanB,beanB 引用 beanA,当我们实例化 beanA 的时候发现 beanB 作为 beanA 的成员对象出现了,那么我们就可能在实例化 beanA 的中间需要先实例化 beanB,然后完成 beanB 的实例化之后,才能完成 beanA 的实例化。
但是 beanB 中也引用了 beanA,在实例化 beanB 过程中又需要实例化 beanA,而 beanA 正在进行实例化,但完成 beanA 的实例化的条件是 beanB 实例化完成,完成 beanB 实例化的条件是完成 beanA 的实例化,于是他们最终反映为一个环状依赖,难以完成实例化。
二、循环依赖的三种情况
2.1 构造器注入形成的循环依赖
beanB 需要在 beanA 的构造函数中完成初始化,beanA 也需要在 beanB 的构造函数中完成初始化,这种情况的结果就是两个 bean 都不能完成初始化,循环依赖难以解决。
2.2 setter 注入构成的循环依赖
beanA 需要在 beanB 的 setter 方法中完成初始化,beanB 也需要在 beanA 的 setter 方法中完成初始化,spring 设计的机制主要就是解决这种循环依赖,也是今天要讨论的重点。
2.3 prototype 作用域 bean 的循环依赖
这种循环依赖同样无法解决,因为 spring 不会缓存 prototype 作用域的 bean,而 spring 中循环依赖的解决方式正是通过缓存来实现的。
三、循环依赖解决方案
spring 循环依赖的理论依据其实是 Java 基于引用传递,当我们获取到对象的引用时,对象的 field 是可以延后设置的。spring 单例对象的初始化其实可以分为三步:实例化、填充属性、初始化。
实例化(createBeanInstance):就是调用对应的构造方法构造对象,此时只是调用了构造方法,并没有进行属性填充。
填充属性(populateBean):填充属性。
初始化(initializeBean):调用 spring xml 中指定的 init 方法,或者 AfterPropertiesSet 方法。
四、spring 中的三级缓存
对于单例对象来说,在 spring 的整个容器的生命周期内,有且只存在一个对象,很容易想到这个对象应该存在 Cache 中,spring 大量运用了 Cache 的手段,在循环依赖问题的解决过程中甚至使用了 "三级缓存"。
// 一级缓存
private final Map<String,Object> singletonObjects = new ConcurrentHashMap<>(256);
// 三级缓存
private final Map<String,ObjectFactory<?>> singletonFactories = new HashMap<>();
// 二级缓存
private final Map<String,Object> earlySingletonObjects = new HashMap<>(16);
4.1 一级缓存
singletonObjects,存放完全实例化属性赋值完成的单例对象的 cache,直接可以使用。
4.2 二级缓存
earlySingletonObjects,存放提前曝光的单例对象的 cache,尚未进行属性封装的 Bean。
4.3 三级缓存
singletonFactories,存放单例对象工厂的 cache。
五、spring 如何解决循环依赖
spring 在创建 bean 的工程中,首先会尝试从缓存中获取,这个缓存指的就是上面的 singletonObjects(一级缓存),主要调用的方法如下:
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 != NULL_OBJECT ? singletonObject : null);}
isSingletonCurrentlyInCreation():判断对应的单例对象是否在创建中,例如:BeanA 在填充属性的过程中依赖了 BeanB 对象,得先去创建 BeanB 对象,此时 BeanA 处于创建中。
allowEarlyReference():是否允许从 singletonFactories(三级缓存)中通过 getObject 拿到对象。
分析下 getSingleton() 的整个过程:
步骤一:
spring 首先从 singletonObjects(一级缓存)中尝试获取。
步骤二:
如果在一级缓存中获取不到并且对象处于创建中状态,则尝试从 earlySingletonObjects(二级缓存) 中获取。
步骤三:
如果还是获取不到并且允许从 singletonFactories(三级缓存)通过 getObject() 获取,则通过 singletonFactory.getObject()(三级缓存) 获取。如果获取到了则移除对应的 singletonFactory,将 singletonObject 放入到 earlySingletonObjects(二级缓存),其实就是将三级缓存提升到二级缓存中!
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
spring 解决循环依赖的诀窍就在于 singletonFactories(三级缓存) 这个 cache,这个 cache 中存的是类型为 ObjectFactory,其定义如下:
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
在 bean 创建过程中,有两处比较重要的匿名内部类实现了该接口,上面已经提到了,spring 利用他来创建 bean
new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
}
}
另一处就是:
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
此处就是解决循环依赖的关键,这段代码发生在 createBeanInstance(实例化)之后,也就是说单例对象此时已经被创建出来的。这个对象已经被生产出来了,虽然还不完美(还没有进行填充属性和初始化),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以 Spring 此时将这个对象提前曝光出来让大家认识,让大家使用。
六、总结:
BeanA 的某个 field 或者 setter 依赖了 BeanB 的实例对象,同时 BeanB 的某个 field 或者 setter 依赖了 BeanA 的实例对象,出现这种循环依赖的情况。
BeanA 首先完成了初始化的第一步(实例化),并且将自己放到 singletonFactories(三级缓存)中,此时进行初始化的第二步(属性填充),发现自己依赖对象 BeanB,此时就尝试去 get(BeanB),发现 BeanB 还没有被 create,所以走 create 流程。
BeanB 在初始化第一步(实例化)完成后,进行第二步属性填充的时候发现自己依赖了对象 BeanA,于是尝试 get(BeanA),尝试从一级缓存 singletonObjects(肯定没有,因为 BeanA 还没初始化完全),尝试从二级缓存 earlySingletonObjects(也没有),尝试从三级缓存 singletonFactories 中获取,由于 BeanA 通过 ObjectFactory 将自己提前曝光了,所以 BeanB 能够通过 ObjectFactory.getObject() 拿到 BeanA 对象(虽然 BeanA 还没有初始化完全,但是可以被发现),
BeanB 拿到 BeanA 对象后顺利完成了初始化阶段1、2、3(实例化、填充属性、初始化),完全初始化之后将自己放入到一级缓存 singletonObjects 中。并且将 BeanA 放到二级缓存中,移除三级缓存中的 BeanA。
此时返回 BeanA 中,BeanA 此时能拿到 BeanB 的对象顺利完成自己的初始化阶段2、3(填充属性和初始化),最终 BeanA 也完成了初始化,将 BeanA 也添加到了一级缓存 singletonObjects 中。
知道了这个原理时候,肯定就知道为啥 Spring 不能解决“BeanA的构造方法中依赖了 BeanB 的实例对象,同时 BeanB 的构造方法中依赖了 BeanA 的实例对象”这类问题了!因为 BeanB 连初始化的第一步(实例化)都完成不了,无法进行下一步的操作。