前言
在分析springBean创建过程的博文中多次提到了”循环依赖“这个名词,这是创建Bean过程中比较难理解的点,所有这里单独写一篇博文分析循环依赖以及通过三级缓存解决部分循环依赖的问题。
什么是循环依赖
循环依赖也就是说多个对象之间互相引用,你中有我我中有你,形成了一个环状,没有终结条件,最终形成死循环。
通过一个类图展示循环依赖的结构,A引用B,B引用C,C引用A 形成环状引用。
Spring中的循环依赖
spring中存在 构造器循环依赖、多例(原型)循环依赖、单例循环依赖 三种循环依赖的场景。
首先说明 只有单例模式且允许提前暴露以及当前bean没有被代理的情况下 单例循环依赖可以通过三级缓存解决,其他两种循环依赖场景暂时没有解决方法,会抛出BeanCurrentlyInCreationException异常。
Spring之所以能够解决单例循环依赖是因为单例bean是共享的,依赖对象是基于引用传递;通俗的来讲,在解决单例循环依赖时,spring先给这个bean创建了一个坑位,其他bean依赖它时,只需要知道这个坑位的地址就行了;暂时不用关心坑里的东西是否完善,只要知道了坑位的坐标就好,需要用的时候就到这个坑里面取;至于坑里的东西是怎么来的,交给坑的主人自己处理吧。
根据“坑位”这个比喻可得知:
构造器循环依赖无法解决是因为连坑位都无法创建,也就没有以后的操作了,所以抛出异常;
多例循环依赖无法解决是因为坑位不是共享的,每个依赖都是单独的坑,后续无法填东西,所以无法解决。
三级缓存
上文提到了单例模式的bean在整个容器中只会存在一个,所以这个bean是共享的,可以缓存起来。整个Spring都是是通过Map实现的缓存,对于三级缓存也不另外,在 DefaultSingletonBeanRegistry 中定义了三个Map来解决单例模式下的循环依赖问题。
/**
* 一级缓存 缓存beanName和bean实例 ,key:beanName,value:beanInstance
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**
* 三级缓存 缓存beanName和beanFactory,key:beanName, vaule:ObjectFactory
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/**
* 二级缓存
* 跟singletonObjects相同的地方是都是用来缓存beanName和bean实例的,
* 不同点在于当一个单例的bean被放在earlySingletonObjects后,那么当bean还是在创建过程中,就可以通过getBean方法取到,目的就是为了处理循环依赖。
*/
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
这个类中还有其他几个属性,一并提前解释一下,接下来的三级缓存的源码实现中会用到。
/**
* 单例模式下Bean的注册表,用于存放已经注册的beaName
* */
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
/**
* 当前正在创建中的beanName 集合
*/
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/**
* 当前从创建检查中排除的 bean 的名称。
*/
private final Set<String> inCreationCheckExclusions =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
/**
* 抑制异常的集合,可用于关联相关原因。
@Nullable
private Set<Exception> suppressedExceptions;
/**
* 指示我们当前是否在 destroySingletons 中的标志
private boolean singletonsCurrentlyInDestruction = false;
/**
* 一次性 bean 实例
private final Map<String, Object> disposableBeans = new LinkedHashMap<>();
/**
* 包含 bean 名称之间的映射:bean 名称到 bean 包含的 bean 名称集
*/
private final Map<String, Set<String>> containedBeanMap = new ConcurrentHashMap<>(16);
/**
* 依赖 bean 名称之间的映射:bean 名称到依赖 bean 名称集
*/
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);
/**
* 依赖 bean 名称之间的映射:bean 名称到依赖 bean 名称集
*/
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);
三级缓存的具体实现
DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
// 检查缓存中是否存在实例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 如果是null,并且正在加载中
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 如果没有这个实例,
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
// 拿到锁后,创建早期引用
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 当某些方法需要提前初始化的时候则会调用 addSingletonFactory方法将对于的ObjectFactory初始化策略存储在SingletonFactories
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 调用预先设定的getObject方法
singletonObject = singletonFactory.getObject();
// 记录在缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
// earlySingletonObjects 和 singletonFactories 互斥
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
首先尝试从singletonObjects中获取,如果没有再从earlySingletonObjects中获取,如果还是没有,再从singletonFactories中通过beanName找到对应的objectFactory,并调用其getObject方法创建bean; 最后加入到earlySingletonObjects,同时从singletonFactories中删除,两者是互斥的,也就是从三级缓存中移到了二级缓存中。
这里的关键部分,就是在创建bean的时候提前暴露ObjectFactory, 才有了三级缓存,才能够在ObjectFactory中通过getBean获取到bean实例。在 Spring源码解读(四)Bean创建过程之加载——AbstractBeanFactory 博文中已经提到过循环依赖,这里是展开讲解。在doCreateBean时,会优先解决循环依赖的问题,就是通过提前暴露的方法解决的。 注意只有满足三个条件才可以加入到三级缓存中: 单例模式、允许提前暴露、单例对象标记为被创建中 。
调用addSingletonBeanRegistry方法是的时机很重要,是在 实例化对象后且在填充属性和执行初始化方法之前 ,也就是上面那个比喻中的“坑位”已经建好了,还没有往里面扔东西的时候。
为什么要在填充属性前呢? 因为在填充属性时,当前bean可能依赖其他bean,如果其他bean没有被创建,就会去创建其他bean,说到这是不是很熟悉,对就是最开始提到的循环依赖的场景。所以要在此之前暴露ObjectFactory,这样子在发生循环依赖的时候,就可以从三级缓存singletonFactories中获取到bean了。
AbstractAutowireCapableBeanFactory#doCreateBean 的中提前暴露ObjectFactory
// 优先解决循环依赖
// 提前暴露ObjectFactory,这里可能会出现 BeanFactoryAware 等生命周期接口触发的情况,但是相比循环依赖,还是要先处理循环依赖
// 必须满足3个条件 单例模式、允许提前暴露、单例对象正在被创建中
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
// 提前暴露给 SingletonFactory
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
DefaultSingletonBeanRegistry#addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
// 操作前先拿到一级缓存的锁
synchronized (this.singletonObjects) {
// 如果一级缓存中存在这个bean,表示已经被创建好,直接跳过
if (!this.singletonObjects.containsKey(beanName)) {
// 一级缓存中不存在,加入到三级缓存中
this.singletonFactories.put(beanName, singletonFactory);
// 同时从二级缓存中删除
this.earlySingletonObjects.remove(beanName);
// 注册到beanName注册表中
this.registeredSingletons.add(beanName);
}
}
}
结合场景案例分析
我们通过Spring解决循环依赖的实现,重新分析一下博文开头提到的循环依赖场景。
spring容器创建单例A,首先根据无参构造实例化A,并且暴露一个ObjectFactory,这个ObjectFactory的作用是调用它的getObject方法能返回A,用于返回一个提前暴露的创建中的bean,并且将A的beanName加入到“当前创建中的缓存中”;然后填充属性B,发现B还没有被创建,开始创建B。
Spring容器创建单例B,首先根据无参构造实例化B,并且暴露一个ObjectFactory,这个ObjectFactory的作用是调用它的getObject方法能返回B,用于返回一个提前暴露的创建中的bean,并且将B的beanName加入到“当前创建中的缓存中”;然后填充属性C,发现C还没有被创建,开始创建C。
Spring容器创建单例C,首先根据无参构造实例化C,并且暴露一个ObjectFactory,这个ObjectFactory的作用是调用它的getObject方法能返回C,用于返回一个提前暴露的创建中的bean,并且将C的beanName加入到“当前创建中的缓存中”;然后填充属性A。
填充属性A时优先从单例缓存中获取,首先从一级缓存中singletonObject获取,显然现在是获取不到的,然后从二级缓存earlySingletonObjects中获取,当然现在也是没有的,最后从三级缓存singletonFactories中获取,因为实例化A后就暴露了一个ObjectFactory,所以三级缓存中是有A的ObjectFactory的。拿到A的ObjectFactory调用其getObject方法即可拿到A的实例化对象了,因为这时候A的实例化对象还没有完成初始化(填充属性和执行初始化方法)的相关操作,所以并不是一个完整的SpringBean,暂时把他放在二级缓存earlySingletonObjects中,同时从三级缓存singletonFacotries中删除。
这时C成功引用的依赖A实例化对象,然后执行C初始化的操作(填充属性和执行初始化方法),执行成功后将C加入到一级缓存singletonObjects中,此时C已经是一个完整的SpringBean,创建完成。
B现在可以获取到C了,然后执行B初始化的操作(填充属性和执行初始化方法),执行成功后将B加入到一级缓存singletonObjects中,此时B已经是一个完整的SpringBean,创建完成。
A现在可以获取到C了,然后执行A初始化的操作(填充属性和执行初始化方法),执行成功后将A加入到一级缓存singletonObjects中,此时A已经是一个完整的SpringBean,创建完成。