zoukankan      html  css  js  c++  java
  • Spring源码解析——循环依赖的解决方案

    关注米兜Java.md

    一、前言

    承接《Spring源码解析——创建bean》《Spring源码解析——创建bean的实例》,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFactory。

    二、ObjectFactory

    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isDebugEnabled()) {
            logger.debug("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
        }
        // 为避免后期循环依赖,可以在bean初始化完成前将创建实例的ObjectFactory加入工厂
        /**
         * getEarlyBeanReference(beanName, mbd, bean)方法:
         * 对bean再一次依赖引用,主要应用SmartInstantiationAwareBeanPostProcessor
         * 其中我们熟知的AOP就是在这里将advice动态织入bean中,若没有则直接返回bean,不做任何处理
         */
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    

    这段代码不是很复杂,但是很多人不是太理解这段代码的作用,而且,这段代码仅从此函数中去理解也很难弄懂其中的含义,我们需要从全局的角度去思考 Spring 的依赖解决办法。
    earlySingletonExposure :从字面的意思理解就是提早曝光的单例,我们暂不定义它的学名叫什么,我们感兴趣的是有哪些条件影响这个值。

    • mbd.isSingleton() :没有太多可以解释的,此 RootBeanDefinition 代表的是否是单例。
    • this.allowCircularReferences :是否允许循环依赖,很抱歉,并没有找到在配置文件中如何配置,但是在 AbstractRefreshableApplicationContext 中提供了设置函数,可以通过硬编码的方式进行设置或者可以通过自定义命名空间进行配置,其中硬编码的方式代码如下。
    ClassPathXmlApplicationContext bf = ClassPathXmlApplicationContext("aspectTest.xml" ); bt.setAllowBeanDefinitionOverriding(false);
    
    • isSingletonCurrentlylncreation(beanName) :该 bean 是否在创建中。在 Spring 中,会有个专门的属性默认为 DefaultSingletonBeanRegistry的 singletonsCurrentlylnCreation 来记录 bean 的加载状态,在 bean 开始创建前会将 beanName 记录在属性中,在 bean 创建结束后会将 beanName 从属性中移除。那么我们跟随代码一路走来可是对这个属性的记录并没有多少印象,这个状态是在哪里记录的呢?不同 scope 的记录位置并不一样,我们以 singleton 为例,在 singleton 下记录属性的函数是在 DefaultSingletonBeanRegistry的 public Object getSingleton(String beanName, ObjectFactory singletonFactory)函数的 beforeSingletonCreation(beanName)和 afterSingletonCreation(beanName)中,在这两段函数中分别this.singletonCurrentlylnCreation.add(beanName)与 this.singletonCurrentlylnCreation.remove(beanName)来进行状态的记录与移除。

    经过以上分析我们了解变量 earl earlySingletonExposure 是否是单例、是否允许循环依赖、是否对应的 bean 正在创建的条件的综合。当这 3 个条件都满足时会执行 addSingletonFactory操作,那么加入 SingletonFactory的作用是什么呢?又是在什么时候调用呢?

    我们还是以最简单的AB循环依赖为例,类A中含有属性类B,而类B中又会含有属性类A,那么初始化beanA的过程如下图所示:
    beanA.md

    上图展示了创建 beanA 的流程,图中我们看到,在创建 A 的时候首先会记录类 A 所对应的 beanName,并将beanA的创建工厂加入缓存中,而在对 A的属性填充也就是调用populate方法的时候又会再一次的对 B 进行递归创建。同样的,因为在 B 中同样存在 A 属性,因此在实例化 B 的的 populate 方法中又会再次地初始化 A ,也就是图形的最后,调用 getBean(A)。关键是在这里,有心的同学可以去找找这个代码的实现方式,我们之前已经讲过,在这个函数中并不是直接去实例化 A ,而是先去检测缓存中是否有已经创建好的对应的 bean ,或者是否已经创建好的 ObjectFactory,而此时对于A的 ObjectFactory我们早已经创建,所以便不会再去向后执行,而是直接调用 ObjectFactory去创建 A 。这里最关键的是 ObjectFactory的实现。

    /**
     * getEarlyBeanReference(beanName, mbd, bean)方法:
     * 对bean再一次依赖引用,主要应用SmartInstantiationAwareBeanPostProcessor
     * 其中我们熟知的AOP就是在这里将advice动态织入bean中,若没有则直接返回bean,不做任何处理
     */
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    

    其中getEarlyBeanReference的代码如下:

    protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }
    

    在 getEarlyBeanReference 函数中并没有太多的逻辑处理,或者说除了后处理器的调用外没有别的处理工作,根据以上分析,基本可以理清 spring 处理循环依赖的解决办法,在 B 中创建依赖 A 时通过 ObjectFactory 提供的实例化方法来中断 A 中的属性填充,使 B 中持有的 A 仅仅是刚刚初始化并没有填充任何属性的 A ,而这正初始化 A 的步骤还是在最开始创建 A 的时候进行的,但是因为 A 与 B 中的 A 所表示的属性地址是一样的,所以在 A 中创建好的属性填充自然可以通过 B 中的 A 获取,这样就解决了循环依赖的问题。

    三、小结

    大体上的原理就是这样,有什么不明白的地方,大伙可继续阅读如下文章:

    https://blog.csdn.net/m0_38043362/article/details/80284577

    https://blog.csdn.net/hzcao/article/details/78479593

    http://book.51cto.com/art/201311/419098.htm

    本文在米兜公众号链接

    https://mp.weixin.qq.com/s/P7f0HLnyjHqoN4-rUm0ytQ

    欢迎关注米兜Java,一个注在共享、交流的Java学习平台。

    米兜Java.md

  • 相关阅读:
    函数如何命名
    jsp/servlet
    hibernate主键生成策略
    Java项目经验(ssh)
    jvm的内存区划分
    @Override
    Java成长简介(转载)
    接口 转载
    SpringBoot项目jar、war方式的部署
    服务注册与发现及其优雅停服
  • 原文地址:https://www.cnblogs.com/midoujava/p/11291487.html
Copyright © 2011-2022 走看看