前面的文章一直在研究Spring创建Bean的整个过程,创建一个bean是一个非常复杂的过程,而其中最难以理解的就是对循环依赖的处理,本文就来研究一下spring是如何处理循环依赖的。

1. 什么是循环依赖

不格 www.vbuge.com

  不管之前是否研究过循环依赖,这里先对这个知识做一点回顾。

  循环依赖就是循环引用,就是两个或者多个bean相互之间的持有对方,比如A引用B,B引用C,C引用A,则它们最终反映为一个环,参考下图:

  了解了什么是循环依赖之后,我们知道这是一种不可避免会出现的情况,那作为Bean容器的Spring又是怎么处理这一问题呢?我们接着往下看。

2. Spring如何处理循环依赖

  Spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器又是如何解决循环依赖的呢?我们来测试一下,首先我们来定义循环引用类:

public class TestA{private TestB testB;public void a(){testB.b();}public TestB getTestB(){return testB;}public void setTestB(TestB testB){this.testB = testB;}
}public class TestB{private TestC testC;public void b(){testC.c();}public TestC getTestC(){return testC;}public void setTestC(TestC testC){this.testC = testC;}
}public class TestC{private TestA testA;public void c(){testA.a();}public TestA getTestA(){return testA;}public void setTestA(TestA testA){this.testA = testA;}
}

  在Spring中将循环依赖的处理分成了3种情况:

2.1 构造器循环依赖处理

  这表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。

  比如在创建TestA类时,构造器需要TestB类,那么将去创建TestB,在创建TestB类时又发现需要TestC类,则又去创建TestC,最终在创建TestC时发现又需要TestA,从而形成一个环,没办法创建。

  Spring容器将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持在这个池中,因此如果在创建bean的过程中发现自己已经在“当前创建bean池”里时,则抛出BeanCurrentlyInCreationException异常表示出现了循环依赖;而对于创建完毕的bean将从“当前创建bean池”中清除掉,这个“当前创建bean池”实际上是一个ConcurrentHashMap,即DefaultSingletonBeanRegistry中的singletonsCurrentlyInCreation。

  我们通过一个直观的测试用例来进行分析:

  xml配置如下:

    <bean id = "testA" class = "xxx.xxx"><constructor-arg index = "0" ref = "testB"/></bean><bean id = "testB" class = "xxx.xxx"><constructor-arg index = "0" ref = "testC"/></bean><bean id = "testC" class = "xxx.xxx"><constructor-arg index = "0" ref = "testA"/></bean>

  创建测试用例:

public static void main(String[] args) {try{new ClassPathXmlApplicationContext("beans.xml");}catch (Exception e){e.printStackTrace();}
}

  这个执行过程中会抛出异常BeanCurrentlyInCreationException,通过debug可以快速找到异常抛出的位置在getSingleton()方法中的beforeSingletonCreation():

protected void beforeSingletonCreation(String beanName) {if (!this.inCreationCheckExclusions.containsKey(beanName) &&this.singletonsCurrentlyInCreation.put(beanName, Boolean.TRUE) != null) {throw new BeanCurrentlyInCreationException(beanName);}
}

  由此可知,Spring在对构造器循环依赖的处理策略上是选择了直接抛异常,而且对循环依赖的判断是发生在加载单例时调用ObjectFactory的getObject()方法实例化bean之前。

2.2 setter循环依赖处理

  这个表示通过setter注入方式构成的循环依赖。对于setter注入造成的循环依赖Spring是通过提前暴露刚完成构造器注入但还未完成其他步骤(如setter注入)的bean来完成的,而且只能解决单例作用域的bean循环代码,我们这里来详细分析一下Spring是如何处理的。

  关于这部分的处理逻辑,在AbstractAutowireCapableBeanFactory的doCreateBean()方法中有一段代码,如下所示:

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
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加入工厂addSingletonFactory(beanName, new ObjectFactory<Object>() {public Object getObject() throws BeansException {// 对bean再一次依赖引用,主要应用SmartInstantiationAwareBeanPostProcessor,// 其中我们熟知的AOP就是在这里将advice动态织入bean中,若没有则直接返回bean,不做任何处理return getEarlyBeanReference(beanName, mbd, bean);}});
}

  这段代码不是很复杂,但是如果是一开始看这段代码的时候不太容易理解其作用,因为仅仅从函数中去理解是很难弄懂其中的含义,这里需要从全局的角度去思考Spring的依赖解决办法才能更好理解。

  • earlySingletonExposure:从字面的意思理解就是是否提早曝光单例
  • mbd.isSingleton():是否是单例
  • this.allowCircularReference:是否允许循环依赖,在AbstractRefreshableApplicationContext中提供了设置函数,可以通过硬编码的方式进行设置或者可以通过自定义命名空间进行配置,硬编码的方式代码如下:
ClassPathXmlApplicationContext bf = new ClassPathXmlApplicationContext("aspectTest.xml");
bf.setAllowBeanDefinitionOverriding(false);
  • isSingletonCurrentlyInCreation(beanName):该bean是否在创建中。在Spring中,会有一个专门的属性(类DefaultSingletonBeanRegistry中的singletonsCurrentlyInCreation)来记录bean的加载状态,在bean开始创建前会将beanName记录在属性中,在bean创建结束后会将beanName从属性中移除。我们跟随代码一路走来或许对这个属性的记录并没有多少印象,不经会拍脑门问这个状态是在哪里记录的呢?不同scope的记录位置并不一样,我们以singleton为例,在singleton下记录属性的函数是在DefaultSingletonBeanRegistry类的getSingleton(String beanName,ObjectFactory singletonFactory)函数中的beforeSingletonCreation(beanName)和afterSingletonCreation(beanName)中,在这两段函数中分别通过this.singlesCurrentlyInCreation.add(beanName)与this.singlesCurrentlyInCreation.remove(beanName)来进行状态的记录与移除。

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

  这里还是用一个最简单的AB循环依赖为例,类A中含有属性类B,而类B中又会含有属性类A,那么初始化beanA的过程如下图所示:

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

  到这里基本可以理清Spring处理循环依赖的解决办法,这里再从代码层面总结一下:

  在创建bean的过程中,实例化bean结束之后,属性注入之前,有一段这样的代码(代码位置为AbstractAutowireCapableBeanFactory类中的doCreateBean()方法中bean实例化之后):

    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");}addSingletonFactory(beanName, new ObjectFactory<Object>() {public Object getObject() throws BeansException {return getEarlyBeanReference(beanName, mbd, bean);}});}

  这段代码前面也说过,主要做的事情是在addSingletonFactory()方法中,即在必要的时候将创建bean的ObjectFactory添加到缓存中。再结合前面的例子来看,在第一次创建beanA时,这里是会将ObjectFactory加入到singletonFactories中,当创建beanB时,在对beanB的属性注入时又会调用getBean()去获取beanA,同样是前面说到过,会先去缓存获取beanA,这时候是可以获取到刚才放到缓存中的ObjectFactory的,这时候就会把实例化好但是还未完成属性注入的beanA找出来注入到beanB中去,这样就解决了循环依赖的问题,需要结合下面的代码细品一下。

protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)throws BeansException {final String beanName = transformedBeanName(name);Object bean;// Eagerly check singleton cache for manually registered singletons.Object sharedInstance = getSingleton(beanName);...
}protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

2.3 prototype范围的依赖处理

  对于"prototype"作用域的bean,Spring容器并不会对其进行缓存,因此无法提前暴露一个创建中的bean,所以也是通过抛出异常的方式来处理循环依赖,这里仍然是用一个demo来测试一下代码是在哪抛的异常。

  配置文件:

<bean id = "testA" class = "xxx" scope = "prototype"><property name = "testB" ref = "testB"/>
</bean>
<bean id = "testB" class = "xxx"><property name = "testC" ref = "testC"/>
</bean>
<bean id = "testC" class = "xxx"><property name = "testA" ref = "testA"/>
</bean>

  测试代码:

public static void main(String[] args) {try{ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");System.out.println(ctx.getBean("testA"));}catch (Exception e){e.printStackTrace();}}

  同样通过断点我们可以定位异常的抛出位置是在AbstractBeanFactory类的doGetBean方法中,在方法开始获取缓存失败之后(prototype不会加入到缓存中),会首先判断prototype的bean是否已创建,如果是就认为存在循环依赖,抛出BeanCurrentlyInCreationException异常。

if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);
}

3. 总结

  Spring中对于循环依赖的处理存在3中场景:

  • 构造器循环依赖处理;
  • setter循环依赖处理;
  • prototype范围的依赖处理;

  其中对于构造器和prototype范围的循环依赖,Spring是直接抛出异常。而对于单例的setter循环依赖,Spring是通过在bean加载过程中提前将bean的ObjectFactory加入到singletonFactories这个缓存用的map中来解决循环依赖的。

spring源码阅读笔记09:循环依赖相关推荐

  1. spring 源码阅读笔记-从浅到深的解析

    目录 第一章 源码安装 文章目录 目录 前言 一.spring源码下载 二.构建源码及使用 1.源码构建 2.使用构建源码 总结 前言 由于spring的源码常常以语言和高深莫测的地位存在,而源码解析 ...

  2. Spring源码阅读笔记(一):整体架构与核心技术

    本篇的主要是根据Spring的官方文档加以整理,旨在理解Spring的整体架构与核心技术的基本概念,建立Spring的基本模型. 1. Spring整体架构 Spring框架是一种分层架构,它包含了一 ...

  3. 代码分析:NASM源码阅读笔记

    NASM源码阅读笔记 NASM(Netwide Assembler)的使用文档和代码间的注释相当齐全,这给阅读源码 提供了很大的方便.按作者的说法,这是一个模块化的,可重用的x86汇编器, 而且能够被 ...

  4. spring源码阅读(3)-- 容器启动之BeanFactoryPostProcessor

    接着上文<spring源码阅读(2)-- 容器启动之加载BeanDefinition>,当spring加载完所有BeanDefinition时,并不会马上去创建bean,而是先配置bean ...

  5. 【Flink】Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型

    1.概述 转载:Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型 相似文章:[Flink]Flink 基于 MailBox 实现的 StreamTask 线程模型 Fl ...

  6. 源码解析:Spring源码解析笔记(五)接口设计总览

    本文由colodoo(纸伞)整理 QQ 425343603 Java学习交流群(717726984) Spring解析笔记 启动过程部分已经完成,对启动过程源码有兴趣的朋友可以作为参考文章. 源码解析 ...

  7. 【Spring 源码阅读】Spring IoC、AOP 原理小总结

    Spring IoC.AOP 原理小总结 前言 版本约定 正文 Spring BeanFactory 容器初始化过程 IoC 的过程 bean 完整的创建流程如下 AOP 的过程 Annotation ...

  8. syzkaller 源码阅读笔记1(syz-extract syz-sysgen)

    文章目录 1. syz-extract 1-0 总结 1-1. `main()` 1-2 `archList()` - `1-1 (3)` 获取架构 name list 1-3 `createArch ...

  9. Spring源码阅读之bean对象的创建过程

    Spring源码阅读之bean对象的创建过程 ​ Spring是通过IOC容器来管理对象的,该容器不仅仅只是帮我们创建了对象那么简单,它负责了对象的整个生命周期-创建.装配.销毁.这种方式成为控制反转 ...

最新文章

  1. 第八天-《企业应用架构模式》-通盘考虑
  2. java序列化库_java 中序列化(Serializable)
  3. Python基础数据类型之字符串(二)
  4. cacti不绘制数据图
  5. OpenCV 霍夫线变换Hough Line Transform
  6. 【NOIP2010】【P1317】乌龟棋
  7. 每日一问:LayoutParams 你知道多少?
  8. opencv+python视频实时质心显示
  9. 比尔盖茨:有种事你永远不能等待!
  10. 域对抗网络Domain adversarial neural network及其应用相关论文
  11. linux ab 命令参数,linux环境中ab命令简介及结果分析
  12. Linux中安装WPS
  13. python 生意参谋_电商 Python 抓取 生意参谋-访客数据
  14. linux centos无线网卡驱动安装,Linux CentOS 7 安装tp link 无线网卡驱动
  15. UTC时间转成北京时间
  16. mmorpg小地图系统制作
  17. 不可不学的摄影技巧之一(构图)
  18. CTF writeup:实验吧,天下武功唯快不破
  19. 2048版俄罗斯方块java_俄罗斯方块版2048
  20. IBM-142-XML认证

热门文章

  1. Linux服务器重启后crs,Linux服务器重启后crs_stat -t 命令无法正常使用以及解决思路...
  2. 北邮计算机考研专业课分值,北邮计算机考研分数线
  3. iOS定位经纬度问题
  4. HQChart使用教程60-新版k线训练使用教程
  5. ictclas4j java_ictclas4j 分词工具包 安装流程
  6. 卡西欧计算机蓝屏的处理方法,电脑出现蓝屏怎么办 电脑出现蓝屏解决方法【图文详解】...
  7. Hive中sort by、distribute by、cluster by的区别及用法
  8. UE4 Gameplay
  9. 华为服务器故障灯不开机_华为手机开不了机指示灯亮,怎么办
  10. ORAN C平面 Section Type 7