### 准备

## 目标

了解 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. 百度地图API开发----手机地图做导航功能

    第一种方式:手机网页点击打开直接进百度地图APP <a href="baidumap://map/direction?mode=[transit:公交,driving:驾车]& ...

  2. Oracle自动备份脚本的实现

    问题描述: Oracle自动备份脚本的实现. 错误提示1: Message file RMAN.msb not found Verify that Oracle_HOME is set properl ...

  3. Docker容器之Nginx

    一,pull一个Nginx镜像 docker pull nginx 二,Nginx镜像文件说明 配置文件 /etc/nginx/nginx.conf 网站根目录 /usr/share/nginx/ht ...

  4. 虚拟机中CentoOs配置ip且连网

    1.修改"VMware Network Adapter VMnet8",配置IP 2.打开虚拟机,"编辑" => "虚拟网络编辑器", ...

  5. Log4j最简入门及实例

    Log4j真的很简单,简单到令人发指的地步.不是要记录日志吗?那就给你一个Log,然后你用Log来写东西就行了,先来一个完整类示例: package test; import org.apache.c ...

  6. ora-28056错误解决

    问题描述:今天有同事找我,说是oracle数据库的监听器出现问题,我连接服务器查看后,发现不是监听器问题,而是进程连接数已经达到150个了,客户端连接不上服务器,因其是测试服务器,重启服务器后再次sq ...

  7. 过千万、亿条数据的mysql表更新 mysql 线程状态

    分段更新 UPDATE question SET `status`=1 WHERE status!=1 LIMIT 3000;UPDATE answer SET `status`=1 WHERE st ...

  8. JAVA参数没有引用传递,只有值传递

    原文章地址:http://www.cnblogs.com/clara/archive/2011/09/17/2179493.html 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性, ...

  9. Windows环境下手动更新boot2docker.iso

    GitHub连不上导致自动更新失败. https://github.com/boot2docker/boot2docker/releases 替换了DockerToolbox安装目录和系统盘用户目录\ ...

  10. Navicat运行sql文件报错out of memory

    下载并安装mysql workbench: