• Spring IOC 源码简单分析 03


    ### 准备

    ## 目标

    了解 Spring 如何处理循环引用

    ##测试代码

    gordon.study.spring.ioc.IOC03_CircularReference.java
     
    ioc03.xml
    <beans ...>
        <bean id="chairman" class="gordon.study.spring.common.Employee">
            <property name="name" value="Cheque Wicket" />
            <property name="company" ref="macrohard" />
        </bean>
        <bean id="macrohard" class="gordon.study.spring.common.Company">
            <property name="name" value="macrohard" />
            <property name="employees">
                <list>
                    <ref bean="chairman" />
                </list>
            </property>
        </bean>
    </beans>

    ### 分析

    ## 问题何在

    按照前一篇 getBean 方法流程分析的结论,当第20行代码通过 getBean 方法获取 chairman bean 实例时,会先实例化 gordon.study.spring.common.Employee 实例,然后装配 name 和 company 属性,由于 company 属性值是另一个 bean macrohard,因此会调用 getBean 方法获取 macrohardbean 实例,巧合的是,macrohard bean 的 employees 属性又依赖 chairman bean 实例,形成循环引用。

    ## 解决思路

    以前自己写过简单的 IOC 容器,处理循环引用的方法就是将已实例化但还未装配完成的 bean 直接当作已完全构造完成的 bean 使用,只要保证这些 bean 最终能够完成装配与初始化工作即可。
    对于本例,chairman bean 在装配 company 属性时依赖 macrohard bean,于是递归构建 macrohard bean,在装配 macrohard bean 的 employees 属性时又依赖回 chairman bean。此时我们把未装配完成的 chairman bean 交给 macrohard bean 用于装配属性,这样macrohard bean 的构建工作就可以继续,直到其完成装配与初始化工作后,递归调用返回(此时macrohard bean 不算完成构建,因为其 employees 中的元素此时还没有被装配与初始化)。现在,chairman bean 就可以用 macrohard bean 完成自己的装配工作了。当chairman bean 装配完成并初始化后,macrohard bean 也自动构建完成了。

    ## 源码分析

    DefaultListableBeanFactory 类型层次如下图所示:
    其父类 DefaultSingletonBeanRegistry 专门用于处理 singleton bean 的注册(包括创建),该类的两个 getSingleton 方法是本次分析的重点
     
    DefaultSingletonBeanRegistry 包含以下属性与本次分析息息相关:
     
    先看一下 getBean 方法的基本逻辑:
    1. 调用 getSingleton(String beanName) 方法尝试获取已构建完成的 bean 实例,或者在发现循环引用时获取 early singleton bean 实例。
    2. 如果前一步 getSingleton 方法没有找到合适的 bean 实例,则通过 getSingleton(String beanName, ObjectFactory<?> singletonFactory) 方法创建 bean 实例。该方法调用过程中会引起递归(创建 bean)。
     
    getSingleton(String beanName, ObjectFactory<?> singletonFactory) 方法主要流程(方法二):
    1. 尝试直接从 singletonObjects 中获取已构建完成的 bean 实例。singletonObjects 用于缓存已完全构建的 bean 实例。
    2. beforeSingletonCreation 方法中将 bean name 放入集合 singletonsCurrentlyInCreation 中。singletonsCurrentlyInCreation 记录已经在创建中的所有单例 bean,当相同的 bean 妄图进入该集合时,表示发现循环引用且无法解决,将抛出 BeanCurrentlyInCreationException 异常。
    3. 调用参数 ObjectFactory 的 getObject 方法创建 bean 实例。
      getObject 方法直接调用 AbstractAutowireCapableBeanFactory createBean 方法创建 bean 实例,包括实例化、属性装配与初始化工作,调用栈如下图所示(截图来自上一篇文章):
      在本例中,因为使用的都是无参构造函数,所以实例化工作是一定可以完成的。
      当实例化完成后,有以下与循环引用相关的代码:

      默认情况下,全局参数允许循环引用(allowCircularReference)值为 true,考虑到当前 bean 在上一步一定已经放入 singletonsCurrentlyInCreation 集合,因此会调用 addSingletonFactory 方法为该 bean 创建对应的 ObjectFactory。【(以下描述不准确)addSingletonFactory方法的实现有点闭包的味道:实际上是建立了 beanName 与 getEarlyBeanReference 方法在当时的上下文环境(可以简单理解为局部变量值环境)下的联系。之后需要获取 beanName 的 early bean reference 时,通过调用 ObjectFactory 的 getObject 方法,相当于调用当时上下文环境下的 getEarlyBeanReference方法(参数值保留了创建 ObjectFactory 实例时上下文环境中的变量值)。可以想象,如果不用闭包,需要将 mbd 与 bean 用其它的方式与 beanName 建立对应关系并缓存起来。实际上对于本例,getEarlyBeanReference方法返回的就是其参数 bean,也就是此段代码前面刚刚创建的 bean 实例。

      如上,bean name 与相应的 ObjectFactory 被放入 singletonFactories 中。同时可以发现,当 beanName 存在于 singletonFactories中时,一定不存在于 earlySingletonObjects 中。
      之后,在装配属性时可能会依赖其它 bean 实例,通过 getBean 方法获取相应的 bean 实例。如果没有循环引用,则形成典型的递归调用(getBean -> 方法二 -> getBean);如果发现循环引用,则方法一介入,见下文分析。
    4. afterSingletonCreation 方法中将 bean name 从 singletonsCurrentlyInCreation 中移除。
    5. addSingleton 方法中将创建出的实例放入 singletonObjects 中。
      第147行将 bean name 从 earlySingletonObjects 中移除。目前看来纯粹是为了释放内存,并无其它深意。
     
    getSingleton(String beanName) 方法主要流程(方法一):
    1. 尝试直接从 singletonObjects 中获取已装配完成的 bean 实例
    2. 如果 bean name 在集合 singletonsCurrentlyInCreation 中,表示发现循环引用。此时,本方法会通过 earlySingletonObjects 要么返回已经创建好的 early singleton bean 实例,要么通过 singletonFactories 找到对应的 ObjectFactory 创建 early singleton bean 实例并放入 earlySingletonObjects 中。

    ## 本例分析

    • 对于本例,首先通过 getBean 方法获取 chairman bean,getSington 方法一返回 null(因为 singletonsCurrentlyInCreation 当前为空,不包含 chairman),getSington 方法二实例化 chairman bean,在装配 company 属性时,需要构造 macrohard bean。此时 singletonObjects 为空,earlySingletonObjects 为空,singletonsCurrentlyInCreation 包含 chairmansingletonFactories 包含 chairman
    • 递归调用 getBean 方法获取 macrohard bean,getSington 方法一返回 null(因为singletonsCurrentlyInCreation当前不包含 macrohardgetSington 方法二实例化 macrohard bean,在装配 employees 属性时,需要构造 chairman bean。此时 singletonObjects 为空,earlySingletonObjects 为空singletonsCurrentlyInCreation 包含 chairman 与 macrohardsingletonFactories 包含 chairman 与 macrohard
    • 再次递归进入 getBean 方法获取 chairman bean,方法一创建 early singleton bean chairman 并放入 earlySingletonObjects此时 singletonObjects 为空,earlySingletonObjects 包含 chairmansingletonsCurrentlyInCreation 包含 chairman 与 macrohardsingletonFactories 包含 macrohard
    • 返回 macrohard bean 构建过程,完成构建。此时 singletonObjects 包含 marcohard,earlySingletonObjects 包含 chairmansingletonsCurrentlyInCreation 包含 chairmansingletonFactories 为空
    • 返回 chairman bean 构建过程,完成构建。此时 singletonObjects 包含 marcohard 与 chairman,earlySingletonObjects 为空singletonsCurrentlyInCreation 为空singletonFactories 为空

    ## 其它循环引用情况分析

    通过 setter 注入方式产生的循环引用是可以通过以上方案解决的。
    构造器注入方式产生的循环引用无法解决,因为无法实例化出 early singleton bean 实例。
    非单例模式的循环引用也无法解决,因为 Spring 框架不会缓存非单例的 bean 实例。
     
     
     
  • 相关阅读:
    资源-python 视频下载大全
    ubuntu 16.04(操作应用) -软件卸载
    资源-简历的相关知识
    centos (命令操作)-crontab命令
    ubuntu 16.04 (软件应用)-输入法
    ntp时间同步
    lvm空间扩容
    目录知识
    Linux下安装maven
    elasticsearch安装pinyin模块
  • 原文地址:https://www.cnblogs.com/gordonkong/p/7360430.html
Copyright © 2020-2023  润新知