对于Spring为什么要采用三级缓存架构解决循环依赖这个问题,Spring官网并没有给出说明,也没有找到设计者的相关设计文档。研究这个问题其实就是对着代码实现猜测设计意图,所以网上就有了各种猜测,并不完全一致。

研究这个问题有什么意义吗?个人认为其实也就是学习一下大佬的设计思想从而提升或者潜在提升个人能力,除此之外,不会对你使用Spring产生任何影响。

但是据说现在好多面试官会问这个问题,那就和你的切身利益高度相关了,不管是哪一种解释,你终归是需要准备一套逻辑清晰、能够解释清楚说得明白的说法的。

所以我们也尝试研究一下这个问题,如果能研究清楚的话,没准可以帮你保命。

Spring为什么要用三级缓存?

Spring通过三级缓存解决循环依赖,其中一级缓存是真正的Spring IoC容器,用来存储最终完成创建的bean,这个是必不可少的(其实不应该叫“缓存”,应该叫IoC容器)。

二级缓存是用来在创建过程中缓存对象的,比如对象A的创建过程中需要依赖对象B,但是对象B尚未创建,所以就必须要首先创建对象B,这个时候对象A还没有完成最终的创建,所以必须找个地方把他缓存起来,因此二级缓存看似也很必要。

那我们能不能不要二级缓存,创建B的时候先把A直接放入一级缓存,即使A不是最终状态,但是后续的逻辑终究是会完成A的创建、使A变为最终状态的。

那究竟为什么非要这个三级缓存呢?

搞清楚这个问题之前,有必要初步了解一下Spring单例bean的生命周期。

Spring单例Bean的生命周期

Spring单例Bean的生命周期这个话题其实是有点复杂的,我们现在还不想深入到这个层面,因此我们暂时不会深入研究生命周期中的每一个过程,只是大概知道Spring的单例Bean创建过程中都包括那些重要节点,每一个节点大概要干啥,就可以了。

插入一点点题外话:Spring之所以这么强大的一个重要原因就是他的PostProcessors,Spring不仅仅是通过反转控制的方式创建了一个IoC容器帮你管理Bean,更重要的是他可以通过各种PostProcessors实现各种各样的功能,其中最重要的就是AOP。

好的接下来进入正题。

Spring单例Bean的创建过程

也就是Spring源码中doCreateBean的的逻辑。

Spring按照如下顺序创建bean实例:

  1. applyBeanPostProcessorsBeforeInstantiation
    实例化前的后置处理器。这个时候目标Bean还没有实例化。
    实例化前的后置处理器通过CustomTargetSource配置,配置之后Spring将bean实例的创建权交给用户,其实就是Spring不负责bean的创建了。个人感觉应该很少有适用的场景,我们还是要分析项目中最常见的场景,所以这部分可以忽略。

    多说一句,实例化前和实例化后的后置处理器(BeforeInstantiation和AfterInstantiation)是InstantiationAwareBeanPostProcessor接口(BeanPostProcessor的子接口)中定义的,类定义的javaDoc中本来就说是Spring Framework内部使用的,不建议用户直接使用。

    This interface is a special purpose interface, mainly for internal use within the framework. It is recommended to implement the plain {@link BeanPostProcessor} interface as far as possible.
  2. applyBeanPostProcessorsAfterInitialization
    初始化后的BeanPostProcessor处理,目标Bean必须已经被BeanPostProcessorsBeforeInstantiation创建。如果实例已经被创建,打标签beforeInstantiationResolved=true。
    同上,依赖于第1步的BeforeInstantiation后置处理器,忽略。
  3. createBeanInstance
    创建Bean实例。
  4. populateBean
    属性填充。
  5. applyBeanPostProcessorsBeforeInitialization
    初始化前的BeanPostProcessors。
    对于实现了BeanPostProcessor并注册到当前容器中的所有BeanPostProcessor,调用其方法postProcessBeforeInitialization。和我们今天的主题关系不大,暂时忽略。
  6. invokeInitMethods
    调用配置的init-method,或者如果bean实现了InitializingBean接口的话则调用afterPropertiesSet方法,执行初始化。
  7. applyBeanPostProcessorsAfterInitialization
    初始化后的BeanPostProcessors。调用所有实现了BeanPostProcessor接口并注册到当前容器的BeanPostProcessor,调用其postProcessAfterInitialization方法。

以上7步,完成bean的创建。

我们重点说明一下第7步,调用BeanPostProcessor的postProcessAfterInitialization方法。我们知道Spring的很多重要功能都是通过BeanPostProcessor实现的,其中就包括AOP。Spring中有几个不同的BeanPostProcessor实现AOP,包括:

image.png

他们都是虚拟类AbstractAutoProxyCreator的扩展类,而AbstractAutoProxyCreator的接口继承关系如下:

AbstractAutoProxyCreator - >SmartInstantiationAwareBeanPostProcessor->InstantiationAwareBeanPostProcessor->BeanPostProcessor。

可以看到AbstractAutoProxyCreator实现了的BeanPostProcessor接口,所以他的postProcessAfterInitialization方法最终会被以上的第7步调用到:

@Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (this.earlyProxyReferences.remove(cacheKey) != bean) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

wrapIfNecessary的作用是,判断当前对象如果需要AOP的话,使用CGLIB创建当前对象的代理对象后返回。

所以我们可以知道第7步applyBeanPostProcessorsAfterInitialization的目的:如果当前对象需要AOP的话,创建代理对象以便实现AOP。

单例Bean的get过程

也就是Spring查找bean的过程。其中就包括我们上面讨论过的7步创建过程。

我们对Spring getBean的讨论要尽可能精简但是必须要包含主要的或者与今天主题相关的逻辑,上面讨论过的7步(doCreateBean的过程)会糅合进来但是已经声明与今天主题无关的步骤就忽略掉了,整理后的getBean的逻辑如下:

  1. getBean入口,首先检查一级缓存如果存在的话,直接返回。否则检查二级缓存存在的话直接返回,否则,检查三级缓存存在的话,用三级缓存中的bean工厂加工bean(参考第3步的解释,实际是在进行AOP的处理)后放入二级缓存,清除三级缓存,返回bean。
  2. 第1步没有拿到bean,则将当前beanName放入“正在创建中”列表,开始创建bean。
  3. createBeanInstance
    创建Bean实例,如果当前bean处于“正在创建中”列表,则创建一个bean工厂放入三级缓存。注意这个bean工厂的目的就是要在工厂方法中实现applyBeanPostProcessorsAfterInitialization方法,也就是正常情况下第7步才要执行的动作。
  4. populateBean
    属性填充。进行依赖对象的查找和注入,查找依赖对象的入口依然是getBean,返回到当前第1步。
  5. applyBeanPostProcessorsBeforeInitialization
    初始化前的BeanPostProcessors。
  6. invokeInitMethods
    调用配置的init-method,执行初始化。
  7. applyBeanPostProcessorsAfterInitialization
    为实现AOP,利用当前原始bean实例创建代理实例。
  8. 当前bean从“正在创建中”列表移除。
  9. 完成bean的创建,bean从一、二级缓存移除,放入一级缓存。

通过以上9步工作法完成单例bean的创建,要了然于心。

为什么要用三级缓存解决循环依赖

绕回这个问题上来了。

虽然做了很多铺垫,解释起来还是不会很容易。但是其实铺垫并不仅仅是为了回答这个问题,其实本人对这个问题本身的价值存疑,而前面几篇文章的铺垫内容的价值远远大于回答这个问题的价值了。

我们尝试回答这个问题,为什么不能去掉二级、或者去掉三级缓存,只用一级或一级、二级缓存解决问题。

Spring用以上9步工作法完成bean的创建,顺利的情况下(没有依赖的bean,或者依赖的bean已经完成创建了)从第1步开始一直到第9步结束,一口气完成bean的创建。

如果有依赖、尤其是有循环依赖的话,bean的创建过程就不会一口气完成以上9个步骤,比如A依赖B、B依赖C、C依赖D,D依赖A这种场景,bean的创建过程就会是(假设bean的创建顺序是A -> B -> C -> D):创建A对象从第1步走到第4步,发现A依赖B,则创建B对象又是从第一步走到第4步...创建C对象从第1步到第4步,创建D对象从第1步到第9步,然后再一次退回去跑C对象的第5步到第9步...一直退回到A对象的创建,跑完第5到第9步。

在整个Bean的创建过程中,正常情况下AOP的处理是放在第7步,也就是bean创建过程的最后来完成的。

在没有循环依赖的情况下AOP放在最后处理是没有问题的,但是如果有循环依赖,比如A依赖B,B依赖A(假设创建顺序是A->B),其实首先完成创建的是B,在B完成创建的时候A的创建进程只走到了第4步,尚未执行AOP处理,这种情况下如果不做特殊处理的话、注入B的A实例本来应该是代理对象、但实际注入的就是原始对象,出问题了。

Spring对这个问题的解决方案就是引入了二级缓存和三级缓存,bean实例创建之后首先创建一个bean工厂放入三级缓存(参考9步工作法中的第3步),如果发生循环依赖的话,在查找依赖对象的时候的第1步中就会在三级缓存中发现这个bean,然后调用工厂方法完成AOP代理对象的处理,之后把处理后的代理对象放入二级缓存。

这样的话Spring就完美解决了循环依赖问题(Sorry,抱歉,不应该这么说,应该说通过上面的解释说明,我们就能理解Spring循环依赖解决方案的逻辑了...)。

所以其实Spring之所以有三级缓存,就是为了解决循环依赖处理过程中的AOP代理对象的创建问题 - 循环依赖的情况下提前创建(所以叫early...)AOP代理对象。

Spring为什么把AOP代理对象的处理放在最后一步?

如果Spring把正常的(没有循环依赖的)Bean创建过程调整一下,把AOP代理对象的创建放在第一步、实例化的时候同步完成,就不会有“为了解决循环依赖处理过程中AOP代理对象的创建问题,从而引入了三级缓存”这个问题了。最多需要二级缓存用来存储依赖查找过程中尚未完成创建的半成品Bean对象。

这可能又是一个是否有价值的问题,不过Spring官网貌似给出了答案,所以就拿出来说明一下,不费力气:

The Spring container guarantees that a configured initialization callback is called immediately after a bean is supplied with all dependencies. Thus, the initialization callback is called on the raw bean reference, which means that AOP interceptors and so forth are not yet applied to the bean. A target bean is fully created first and then an AOP proxy (for example) with its interceptor chain is applied. If the target bean and the proxy are defined separately, your code can even interact with the raw target bean, bypassing the proxy. Hence, it would be inconsistent to apply the interceptors to the init method, because doing so would couple the lifecycle of the target bean to its proxy or interceptors and leave strange semantics when your code interacts directly with the raw target bean.

上面这段话是在将Spring讲init方法的章节中、说明init方法在什么阶段被Spring回调执行。对应我们上面9步工作法的第6步,AOP代理对象的处理在第7步,所以init方法在AOP代理对象处理之前。

这一大段english读起来比较吃力,翻译一下:
image.png

里面提到一个说法:目标Bean的实例化和AOP代理对象的处理是分离、解耦的,这种设计可以给使用者(也就是我们啦)极大地灵活性:比如不触发任何AOP拦截器的情况下直接访问原始目标Bean的初始化方法!

那循环依赖是不是就破坏了Spring的这一设计呢?我觉得我的助手对这一问题的回答还是很有水平的:
image.png

总结

貌似我们已经对“Spring为什么要用三级缓存解决循环依赖”这个问题解释的很清楚了。

但是我觉得还有一个问题:可以去掉二级缓存吗?利用三级缓存创建AOP代理对象之后(没有AOP代理的话还是原对象),不放入二级缓存、直接放入一级缓存,可以吗?

抱歉,本人尚未找到不可以的强硬理由。但是加入二级缓存之后,整个bean的创建逻辑更加清晰了:一级缓存中存储的是最终完成创建的bean,可以直接拿来使用了,二级缓存中的bean是尚未进行属性填充、初始化前后置处理器、初始化、初始化后后置处理器等一些列处理,只是个半成品。

上一篇 Spring FrameWork从入门到NB -三级缓存解决循环依赖内幕 (二)
下一篇 Spring FrameWork从入门到NB - 定制Bean


45 声望17 粉丝