spring源码阅读笔记09:循环依赖
前面的文章一直在研究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:循环依赖相关推荐
- spring 源码阅读笔记-从浅到深的解析
目录 第一章 源码安装 文章目录 目录 前言 一.spring源码下载 二.构建源码及使用 1.源码构建 2.使用构建源码 总结 前言 由于spring的源码常常以语言和高深莫测的地位存在,而源码解析 ...
- Spring源码阅读笔记(一):整体架构与核心技术
本篇的主要是根据Spring的官方文档加以整理,旨在理解Spring的整体架构与核心技术的基本概念,建立Spring的基本模型. 1. Spring整体架构 Spring框架是一种分层架构,它包含了一 ...
- 代码分析:NASM源码阅读笔记
NASM源码阅读笔记 NASM(Netwide Assembler)的使用文档和代码间的注释相当齐全,这给阅读源码 提供了很大的方便.按作者的说法,这是一个模块化的,可重用的x86汇编器, 而且能够被 ...
- spring源码阅读(3)-- 容器启动之BeanFactoryPostProcessor
接着上文<spring源码阅读(2)-- 容器启动之加载BeanDefinition>,当spring加载完所有BeanDefinition时,并不会马上去创建bean,而是先配置bean ...
- 【Flink】Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型
1.概述 转载:Flink 源码阅读笔记(20)- Flink 基于 Mailbox 的线程模型 相似文章:[Flink]Flink 基于 MailBox 实现的 StreamTask 线程模型 Fl ...
- 源码解析:Spring源码解析笔记(五)接口设计总览
本文由colodoo(纸伞)整理 QQ 425343603 Java学习交流群(717726984) Spring解析笔记 启动过程部分已经完成,对启动过程源码有兴趣的朋友可以作为参考文章. 源码解析 ...
- 【Spring 源码阅读】Spring IoC、AOP 原理小总结
Spring IoC.AOP 原理小总结 前言 版本约定 正文 Spring BeanFactory 容器初始化过程 IoC 的过程 bean 完整的创建流程如下 AOP 的过程 Annotation ...
- 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 ...
- Spring源码阅读之bean对象的创建过程
Spring源码阅读之bean对象的创建过程 Spring是通过IOC容器来管理对象的,该容器不仅仅只是帮我们创建了对象那么简单,它负责了对象的整个生命周期-创建.装配.销毁.这种方式成为控制反转 ...
最新文章
- 使用Python PIL库实现简单验证码的去噪处理
- wxpython按钮形状如何修改_Python图形化界面入门教程 - 使用wxPython自定义表
- 自适应均衡器 matlab程序,基于lms自适应均衡器matlab仿真
- EF中创建、使用Oracle数据库的Sequence(序列)功能
- Linux下多进程服务端客户端模型二(粘包问题与一种解决方法)
- 提高MySQL数据库查询效率的几个技巧(转载)
- matlab consumption,Lesage matlab 空间
- FLASH寿命问题说明
- SAP Hybris企业培训
- 新手入门指南 | 教你如何写研报?
- Vue2.js:v-click-outside自定义指令和vue-click-outside监听鼠标点击元素外部区域事件
- python 头条视频_Python视频爬虫实现下载头条视频
- php5时区,PHP5 时区设置方法详解
- 有没有人可以解答一下?
- iView的table表格购物车的使用案例
- CMD命令下修改和查看IP地址,DNS,网关
- Linux:ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cann
- JQ中的“对象不支持此方法或属性”
- 【探究网络安全与网络安全文化及网络安全防范】计算机网络安全现状
- 鸿蒙os流畅性,全球首批!升级使用鸿蒙OS真实反馈评价出炉:“差评好评“都很真实...