### 准备

## 目标

了解 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 包含 chairman,singletonFactories 包含 chairman。
  • 递归调用 getBean 方法获取 macrohard bean,getSington 方法一返回 null(因为singletonsCurrentlyInCreation当前不包含 macrohard),getSington 方法二实例化
    macrohard bean,在装配 employees 属性时,需要构造 chairman bean。此时 singletonObjects 为空,earlySingletonObjects 为空,singletonsCurrentlyInCreation 包含 chairman 与 macrohard,singletonFactories 包含 chairman 与 macrohard。
  • 再次递归进入 getBean 方法获取 chairman bean,方法一创建 early singleton bean chairman 并放入 earlySingletonObjects。此时 singletonObjects 为空,earlySingletonObjects 包含 chairman,singletonsCurrentlyInCreation 包含 chairman 与 macrohard,singletonFactories 包含 macrohard。
  • 返回 macrohard bean 构建过程,完成构建。此时 singletonObjects 包含 marcohard,earlySingletonObjects 包含 chairman,singletonsCurrentlyInCreation 包含 chairman,singletonFactories 为空。
  • 返回 chairman bean 构建过程,完成构建。此时 singletonObjects 包含 marcohard 与 chairman,earlySingletonObjects 为空,singletonsCurrentlyInCreation 为空,singletonFactories 为空。

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

通过 setter 注入方式产生的循环引用是可以通过以上方案解决的。

构造器注入方式产生的循环引用无法解决,因为无法实例化出 early singleton bean 实例。
非单例模式的循环引用也无法解决,因为 Spring 框架不会缓存非单例的 bean 实例。
 
 
 
 
 
 
 

Spring IOC 源码简单分析 03 - 循环引用的更多相关文章

  1. Spring IOC 源码简单分析 01 - BeanFactory

    ### 准备 ## 目标 了解 Spring IOC 的基础流程 ## 相关资源 Offical Doc:http://docs.spring.io/spring/docs/4.3.9.RELEASE ...

  2. Spring IOC 源码简单分析 02 - Bean Reference

    ### 准备 ## 目标 了解 bean reference 装配的流程 ##测试代码 gordon.study.spring.ioc.IOC02_BeanReference.java   ioc02 ...

  3. Spring IOC 源码简单分析 04 - bean的初始化

      ### 准备 ## 目标 了解 Spring 如何初始化 bean 实例 ##测试代码 gordon.study.spring.ioc.IOC04_Initialization.java publ ...

  4. Spring Ioc源码分析系列--自动注入循环依赖的处理

    Spring Ioc源码分析系列--自动注入循环依赖的处理 前言 前面的文章Spring Ioc源码分析系列--Bean实例化过程(二)在讲解到Spring创建bean出现循环依赖的时候并没有深入去分 ...

  5. Spring IOC 源码分析

    Spring 最重要的概念是 IOC 和 AOP,本篇文章其实就是要带领大家来分析下 Spring 的 IOC 容器.既然大家平时都要用到 Spring,怎么可以不好好了解 Spring 呢?阅读本文 ...

  6. spring IoC源码分析 (3)Resource解析

    引自 spring IoC源码分析 (3)Resource解析 定义好了Resource之后,看到XmlFactoryBean的构造函数 public XmlBeanFactory(Resource  ...

  7. Spring Ioc源码分析系列--Ioc源码入口分析

    Spring Ioc源码分析系列--Ioc源码入口分析 本系列文章代码基于Spring Framework 5.2.x 前言 上一篇文章Spring Ioc源码分析系列--Ioc的基础知识准备介绍了I ...

  8. Spring Ioc源码分析系列--Ioc容器BeanFactoryPostProcessor后置处理器分析

    Spring Ioc源码分析系列--Ioc容器BeanFactoryPostProcessor后置处理器分析 前言 上一篇文章Spring Ioc源码分析系列--Ioc源码入口分析已经介绍到Ioc容器 ...

  9. Spring Ioc源码分析系列--Bean实例化过程(一)

    Spring Ioc源码分析系列--Bean实例化过程(一) 前言 上一篇文章Spring Ioc源码分析系列--Ioc容器注册BeanPostProcessor后置处理器以及事件消息处理已经完成了对 ...

随机推荐

  1. jQuery --- 收集表单

    第一种:常用获取对应表单的value值进行收集: 第二种:用jQuery的 serializeArray() 方法收集: <form id="change"> < ...

  2. jquery插件方式实现table查询功能

    1.写插件部分,如下: ;(function($){ $.fn.plugin = function(options){ var defaults = { //各种属性,各种参数 } var optio ...

  3. 在github上参与开源项目日常流程

    转载自:http://blog.csdn.net/five3/article/details/9307041 1. 注册帐号 打开https://github.com/,填写注册信息并提交. 2. 登 ...

  4. yii2 controller发送json数据给前端

    最近要用yii2把之前老项目重构一下,因为前端打算用vuejs,所以Yii2前端那一套就放弃了,直接给前端传json数据 控制器代码: $response = Yii::$app->respon ...

  5. No module named pip.req

    https://stackoverflow.com/questions/25192794/no-module-named-pip-req#

  6. talib 中文文档(七):Overlap Studies Functions

    Overlap Studies Functions 重叠指标 BBANDS - Bollinger Bands 函数名:BBANDS 名称: 布林线指标 简介:其利用统计原理,求出股价的标准差及其信赖 ...

  7. COURSES---poj1469 hdu1083(最大匹配)

    题目链接:http://poj.org/problem?id=1469   http://acm.hdu.edu.cn/showproblem.php?pid=1083 题意:有n个学生p门课, 每门 ...

  8. 安装Ruby、Sass在WebStrom添加Watcher实现编辑scss文件时自动生成.map和压缩后的.css文件

    前言 这段时间一直在看Bootstrap,V3官方直接提供了Less版本的源码,就先将Less学完了,很简单的语法,学习写Demo都是在Webstorm里写的,配置了Watcher自动编译(详见< ...

  9. EOS 的网站及资料doc

    https://github.com/EOSIO/Documentation/blob/master/zh-CN/Roadmap.md https://bytemaster.github.io/bit ...

  10. oracel 服务详细介绍

    中的方法成功安装Oracle 11g后,共有7个服务, 这七个服务的含义分别为: 1. Oracle ORCL VSS Writer Service: Oracle卷映射拷贝写入服务,VSS(Volu ...