从设计角度,深刻分析 Spring 循环依赖的解决思路

前言

Spring 的循环依赖已经被说烂了,可能不少人也看吐了。但不少博客上说的仍是不够清楚,没有完整的表达出 Spring 的设计目的。只介绍了 What ,对于 Why 的介绍却不太够。java

本文会从设计角度,一步一步详细分析 Spring 这个“三级缓存”的设计原则,说说为何要这么设计。spring

Bean 建立流程

Spring 中的每个 Bean 都由一个BeanDefinition 建立而来,在注册完成 BeanDefinition 后。会遍历BeanFactory中的 beanDefinitionMap 对全部的 Bean 调用 getBean 进行初始化。缓存

简单来讲,一个 Bean 的建立流程主要分为如下几个阶段:markdown

  1. Instantiate Bean - 实例化 Bean
  2. Populate Bean - 处理 Bean 的属性依赖,能够是Autowired注入的,也能够是 XML 中配置的,或者是手动建立的 BeanDefinition 中的依赖(propertyValues)
  3. Initialize Bean - 初始化 Bean,执行初始化方法,执行 BeanPostProcessor 。这个阶段是各类 Bean 的后置处理,好比 AOP 的代理对象替换就是在这个阶段

在完成上面的建立流程后,将 Bean 添加到缓存中 - singletonObjects,之后在 getBean 时先从缓存中查找,不存在才建立。app

Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
复制代码

先抛开 Spring 的源码不谈,先看看按照这个建立流程执行会遇到什么问题函数

1. Instantiate Bean

首先是第一阶段 - 实例化,就是调用 Bean Class的构造函数,建立实例而已,没啥可说的。至于一些获取 BeanDefinition 构造方法的逻辑,不是循环依赖的重点。post

2. Populate Bean

第二阶段 - 填充Bean,其目的是查找当前 Bean 引用的全部 Bean,利用 BeanFactory 获取这些 Bean,而后注入到当前 Bean 的属性中。this

正常状况下,没有循环引用关系时没什么问题。好比如今正在进行 ABean 的 populate 操做,发现了 BBean 的引用,经过 BeanFactory 去 getBean(BBean) 就能够完成,哪怕如今 BBean 尚未建立,在getBean中完成初始化也能够。完成后将 BBean 添加到已建立完成的 Bean 缓存中 - singletonObjects。spa

ben_cyclic_ref_0 (1).png

最后再将获取的 BBean 实例注入到 ABean 中就完成了这个 populate 操做,看着还挺简单。设计

此时引用关系发生了点变化,ABean 也依赖了 BBean,两个 Bean 的引用关系变成了互相引用,以下图所示:ben_cyclic_ref_1.png 再来看看如今 populate 该怎么执行:

首先仍是先初始化 BBean ,而后发现了 Bbean 引用的 ABean,如今 getBean(ABean),发现 ABean 也没有建立,开始执行对 ABean 的建立:先实例化,而后对 ABean 执行 populate,可 populate 时又发现了 ABean 引用了 BBean,可此时 BBean 尚未建立完成,Bean 缓存中也并不存在。这样就出现死循环了,两个 Bean 相互引用,populate 操做彻底无法执行。

其实解决这个问题也很简单,出现死循环的关键是两个 Bean 互相引用,populate 时另外一个 Bean 还在建立中,没有建立完成。

只须要增长一个中间状态的缓存容器,用来存储只执行了 instantiate 还未 populate 的那些 Bean。到了populate 阶段时,若是完整状态缓存中不存在,就从中间状态缓存容器查找一遍,这样就避免了死循环的问题。

以下图所示,增长了一个中间状态的缓存容器 - earlySingletonObjects,用来存储刚执行 instantiate 的 Bean,在 Bean 完成建立后,从 earlySingletonObjects 删除,添加到 singletonObjects 中。 ben_cyclic_ref_1 (1).png 回到上面的例子,若是在 ABean 的 populate 阶段又发现了 BBean 的引用,那么先从 singletonObjects 查找,若是不存在,继续从 earlySingletonObjects 中查找,找到之后注入到 ABean 中,而后 ABean 建立完成(BeanPostProcessor待会再说)。如今将 ABean 添加到 singletonObjects 中,接着返回到建立 BBean 的过程。最后把返回的 ABean 注入到 BBean 中,就完成了 BBean 的 populate 操做,以下图所示:

ben_cyclic_ref_7 (1).png

循环依赖的问题,就这么轻易的解决了,看着很简单,只是加了一个中间状态而已。但除了 instantiate 和 populate 阶段,还有最后一个执行 BeanPostProcessor 阶段,这个阶段可能会加强/替换原始 Bean

3. Initialize Bean

这个阶段分为执行初始化方法 - initMethod,和执行 BeanFactory 中定义的 BeanPostProcessor(BPP)。执行初始化方法没啥可说的,重点看看 执行BeanPostProcessor 部分。

BeanPostProcessor 算是 Spring 的灵魂接口了,不少扩展的操做和功能都是经过这个接口,好比 AOP。在populate 完成以后,Spring 会对 Bean 顺序执行全部的 BeanPostProcessor,而后返回 BeanPostProcessor 返回的新 Bean 实例(可能有修改也可能没修改)

//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization
public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) throws BeansException {

    Object result = existingBean;
    //对当前 Bean 顺序的执行全部的 BeanPostProcessor,并返回
    for (BeanPostProcessor processor : getBeanPostProcessors()) {
        Object current = processor.postProcessBeforeInitialization(result, beanName);
        if (current == null) {
            return result;
        }
        result = current;
    }
    return result;
}
复制代码

Spring 的 AOP 加强功能,也是利用 BeanPostProcessor 完成的,若是该 Bean 有 AOP 加强的配置,那么执行完 BeanPostProcessor 以后就会返回一个新的 Bean,最后存储到 singletonObjects 中的也是这个加强以后的 Bean

可咱们上面的“中间状态缓存”解决方案中,存储的却只是刚执行完成 instantiate 的 Bean。若是在上面循环依赖的例子中,populate ABean 时因为 BBean 只完成了实例化,因此会从 earlySingletonObjects 获取只完成初始化的 BBean 并注入到 ABean中。

若是 BBean 有 AOP 的配置,那么此时注入到 ABean 中的 只是一个只实例化未 AOP 加强的对象。当 BBean 执行 BeanPostProcessor 后,又会建立一个加强的 BBean 实例,最终添加到 singletonObjects 中的,是加强的 BBean 实例,而不是那个刚实例化的 BBean 实例

以下图所示,ABean 中注入的是黄色的只完成了初始化的 Bbean,而最终添加到 singletonObjects 倒是执行完 AOP 的加强 BBean 实例:

ben_cyclic_ref_2.png

因为 populate 以后还有一步 BeanPostProcessor 的加强,致使咱们上面的解决方案无效了。但也不是彻底无解,若是可让加强型的 BeanPostProcessor 提早执行,而后添加到“中间状态的缓存容器”中,是否是也能够解决问题?

不过并非全部的 Bean 都有 AOP(及其余执行 BPP 后返回新对象) 的需求,若是让全部 Bean 都提早执行 BeanPostProcessor 并不合适。

因此这里能够采用一种“延迟处理”的方式,在中间增长一层 Factory,在这个 Factory 中完成“提早执行”的操做。

若是没有提早调用某个 Bean 的 “延迟处理”Factory,那么就不会致使提早执行 BeanPostProcessor,只有循环依赖场景下,才会出现这种只完成初始化却未彻底建立的 Bean ,才会调用这个 Factory。这个 Factory 的模式就叫延迟处理,若是不调用 Factory 就不会提早执行 BPP。

instantiate 完成后,把这个 Factory 添加到“中间状态的缓存容器”中;这样当发生循环依赖时,原先获取的中间状态 Bean 实例就会变成这个 Factory,此时执行这个Factory 就能够完成“提早执行 BeanPostProcessor”的操做,而且获取执行后的新 Bean 实例

如今增长一个 ObjectFactory,用来实现延迟处理:

public interface ObjectFactory<T> {

	T getObject() throws BeansException;

}
复制代码

而后再建立一个 singletonFactories,做为咱们新的中间状态缓存容器,不过这个容器存储的并非 Bean 实例,而是建立 Bean 的实现代码

private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
复制代码

如今再写一个“提早执行”BeanPostProcessor 的 ObjectFactory,添加到 singletonFactories 中。

//完成bean 的 instantiate 后
//建立一个对该 Bean 提早执行 BeanPostProcessor 的 ObjectFactory
//最后添加到 singletonFactories 中
addSingletonFactory(beanName, 
                    () -> getEarlyBeanReference(beanName, mbd, bean)
                   );

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            ......
        }
    }
}

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        //提早执行 BeanPostProcessor
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}
复制代码

再次回到上面循环依赖的例子,若是在对 ABean 执行 populate 时,又发现了 BBean 的引用,那么此时先从咱们这个新的延迟处理+提早执行的缓存容器中查找,不过如今找到的已经再也不是一个 BBean 实例了,而是咱们上面定义的那个getEarlyBeanReference 的 ObjectFactory ,经过调用 ObjectFactory.getObject() 来获取提早执行 BeanPostProcessor 的这个 ABean 实例。

以下图所示,在对 ABean 执行 populate 时,发现了对 BBean 的引用,那么直接从 singletonFactories 中查找 BBean 的 ObjectFactory 并执行,获取 BeanPostProcessor 加强/替换后的新 Bean

ben_cyclic_ref_3 (1).png

如今因为咱们的中间状态数据从 Bean 实例变成了 ObjectFactory,因此还须要在初始化以后,再检查一下 singletonFactories 是否有当前 Bean,若是有的化须要手动调用一下 getObject 来获取最终的 Bean 实例。

经过**“延迟执行”+“提早执行”**两个操做,终于解决了这个循环依赖的问题。不过提早执行 BeanPostProcessor 会致使最终执行两遍 BeanPostProcessor ,这个执行两遍的问题还须要处理。

这个问题解决倒还算简单,在那些会更换原对象实例的 BeanPostProcessor 中增长一个缓存,用来存储已经加强的 Bean ,每次调用该 BeanPostProcessor 的时候,若是缓存中已经存在那就说明建立过了,直接返回上次建立的便可。Spring 为此还单独设计了一个接口,命名也很形象 - SmartInstantiationAwareBeanPostProcessor

若是你定义的 BeanPostProcessor 会加强并替换原有的 Bean 实例,必定要实现这个接口,在实现内进行缓存,避免重复加强

貌似如今问题已经解决了,一开始设计的 earlySingletonObjects 也不须要了,直接使用咱们这个中间状态缓存工厂 - singletonFactories 就搞定了问题。

不过……若是依赖关系再复杂一点,好比像下面这样,ABean 中有两个属性都引用了 BBean ben_cyclic_ref_0 (2).png 那么在对 ABean 执行 populate 时,先处理 refB 这个属性;此时从 singletonFactories 中查找到 BBean 的这个提早执行 BeanPostProcessor 的 ObjectFactory,调用 getObject 获取到提早执行 BeanPostProcessor 的 BBean 实例,注入到 refB 属性中。

那到了 refB1 这个属性时,因为 BBean 仍是一个没有建立完成的状态(singletonObjects 中不存在),因此仍然须要获取 BBean 的 ObjectFactory,执行 getObject,致使又对 BBean 执行了一遍 BeanPostProcessor。

为了处理这个屡次引用的问题,仍是须要有一个中间状态的缓存容器 - earlySingletonObjects。不过这个缓存容器和一开始提到的那个 earlySingletonObjects 有一点点不一样;一开始提到的 earlySingletonObjects 是存储只执行了 instantiate 状态的 Bean 实例,而咱们如今存储的是执行 instantiate 以后,又提早执行了 BeanPostProcessor 的那些 Bean。

在提早执行了 BeanPostProcessor 以后,将返回的新的 Bean 实例也添加到 earlySingletonObjects 这个缓存容器中。这样就算处于中间状态时有屡次引用(屡次 getBean),也能够从 earlySingletonObjects 获取已经执行完 BeanPostProcessor 的那个 Bean,不会形成重复执行的问题。

总结

回顾一下上面一步步解决循环依赖的流程,最终咱们经过一个延迟处理的缓存容器,加一个提早执行完毕BeanPostProcessor 的中间状态容器就完美解决了循环依赖的问题

至于 singletonObjects 这个缓存容器,它只用来存储全部建立完成的 Bean,和处理循环依赖关系并不大。

至于这个处理机制,叫不叫“三级缓存”……见仁见智吧,Spring 在源码/注释中也没有(3-level cache之类的字眼)。并且关键的循环依赖处理,只是“二级”(延迟处理的 Factory + 提早执行 BeanPostProcessor 的Bean),所谓的“第三级”是应该是指 singletonObjects。

下面用一张图,简单总结一下处理循环依赖的核心机制: ben_cyclic_ref_5.png 不过提早执行 BeanPostProcessor 这个操做,算不算打破了原有的设计呢?本来 BeanPostProcessor 但是在建立Bean 的最后阶段执行的,可如今为了处理循环依赖,给移动到 populate 以前了。虽然是一个不太优雅的设计,但用来解决循环依赖也不错。

尽管 Spring 支持了循环依赖(仅支持属性依赖方式,构造方法依赖不支持,由于实例化都完成不了),但实际项目中,这种循环依赖的关系每每是不合理的,应该从设计上就避免。

原创不易,禁止未受权的转载。若是个人文章对您有帮助,就请点赞/收藏/关注鼓励支持一下吧❤❤❤❤❤❤