本文结合《Spring源码深度解析》来分析Spring 5.0.6版本的源代码。若有描述错误之处,欢迎指正。

实例化bean是一个非常复杂的过程,而其中比较难以理解的就是对循环依赖的解决, 不管之前读者有没有循环依赖方面的研究,这里有必要先对此知识点稍作回顾。

一. 什么是循环依赖

循环依赖就是循环引用,就是两个或多个bean相互之间的持有对方,比如CirdeA引用 CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环。此处不是循环调用,循环调用是方法之间的环调用,如下图所示。

循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。

二. Spring如何解决循环依赖

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

public class TestA {

    private TestB testB;

    public TestA() {
} 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 TestB() {
} 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 TestC() {
} public void c() {
testA.a();
} public TestA getTestA() {
return testA;
} public void setTestA(TestA testA) {
this.testA = testA;
}
}

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

1. 构造器循环依赖

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

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

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

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

(1)创建配置文件。

<bean id = "testA" class = "com.bean.TestA">
<constructor-arg index = "0" ref = "testB"/>
</bean>
<bean id = "testB" class = "com.bean.TestB">
<constructor-arg index="0" ref = "testC"/>
</bean>
<bean id = "testC" class = "com.bean.TestC">
<constructor-arg index="0" ref="testA"/>
</bean>

(2)创建测试用例。

@Test(expected = BeanCurrentlylnCreationException.class)
public void testCircleByConstructor() throws Throwable {
try {
new ClassPathXmlApplicationContext("test-xml");
} catch(Exception e) {
// 因为要在创建testC时抛出
Throwable el = e.getCause().getCause().getCause{);
throw el;
}
}

针对以上代码的分析如下:

  • Spring容器创建“testA” bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数“testB”,并将“testA”标识符放到“当前创建bean池”。
  • Spring容器创建“testB” bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数“testC”,并将“testB”标识符放到“当前创建bean池。
  • Spring容器创建“testC” bean,首先去“当前创建bean池”查找是否当前bean正在创建,如果没发现,则继续准备其需要的构造器参数“testA”,并将“testC”标识符放到“当前创建Bean池”。
  • 到此为止Spring容器要去创建“testA” bean,发现该bean标识符在“当前创建bean 池”中,因为表示循环依赖,抛出BeanCurrentlylnCreationException。

2. setter循环依赖

表示通过setter注入方式构成的循环依赖。对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注人但未完成其他步骤(如setter注人)的bean来完成的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean,如下代码所示:

addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansCxception {
return getEarlyBeanReference(beanName, mbdr bean);
});

具体步骤如下:

(1)Spring容器创建单例“testA” bean,首先根据无参构造器创建bean,并暴露一个 “ObjectFactory”用于返回一个提前暴露的创建中的bean,并将“testA”标识符放到“当前创建bean池”,然后进行setter注人“testB”。

(2)Spring容器创建单例“testB” bean,首先根据无参构造器创建bean,并暴露一个 “ObjectFactory”用于返回一个提前暴露的创建中的bean,并将“testB”标识符放到“当前创建bean池”,然后进行setter注人“testC”。

(3)Spring容器创建单例“testC” bean,首先根据无参构造器创建bean,并暴露一个 “ObjectFactory”用于返回一个提前暴露的创建中的bean,并将“testC”标识符放到“当前创建bean池',然后进行setter注人“testA”。进行注人“testA”时由于提前暴露了 “ObjectFactory” 工厂,从而使用它返回提前暴露的创建中的bean。

(4)最后在依赖注入“testB”和“testA”,完成setter注人。

3. prototype范围的依赖处理

对于“prototype”作用域bean,Spring容器无法完成依赖注人,因为Spring容器不进行缓存“prototype”作用域的bean,因此无法提前暴露一个创建中的bean。示例如下:

(1)创建配置文件。

<bean id = "testA" class = "com.bean.CircleA" scope = "prototype">
<property name = "testB" ref = "testB"/>
</bean>
<bean id = "testB" class = "com.bean.CircleB" scope = "prototype">
<property name = "testC" ref = "testC"/>
</bean>
<bean id = "testC" class = "com.bean.CircleC" scope="prototype">
<property name = "testA" ref = "testA"/>
</bean>

(2)创建测试用例。

@Test(expected = BeanCurrentlylnCreationException.class)
public void testCircleBySetterAndPrototype() throws Throwable {
try {
ApplicationContext context = new ClassPathXmlApplicationContext("test-prototype.xml");
System.out.println(context.getBean("testA"));
} catch(Exception e) {
Throwable el = e.getCause().getCause().getCause{);
throw el;
}
}

对于 “singleton” 作用域 bean,可以通过 “setAllowCircularReferences(false); ”来禁用循环引用。

Spring源码分析(十七)循环依赖的更多相关文章

  1. Spring源码分析之循环依赖及解决方案

    Spring源码分析之循环依赖及解决方案 往期文章: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostPro ...

  2. Spring源码-IOC部分-循环依赖-用实例证明去掉二级缓存会出现什么问题【7】

    实验环境:spring-framework-5.0.2.jdk8.gradle4.3.1 Spring源码-IOC部分-容器简介[1] Spring源码-IOC部分-容器初始化过程[2] Spring ...

  3. Spring 源码分析之 bean 依赖注入原理(注入属性)

         最近在研究Spring bean 生命周期相关知识点以及源码,所以打算写一篇 Spring bean生命周期相关的文章,但是整理过程中发现涉及的点太多而且又很复杂,很难在一篇文章中把Spri ...

  4. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  5. Spring源码分析——BeanFactory体系之抽象类、类分析(二)

    上一篇分析了BeanFactory体系的2个类,SimpleAliasRegistry和DefaultSingletonBeanRegistry——Spring源码分析——BeanFactory体系之 ...

  6. Spring源码分析——BeanFactory体系之抽象类、类分析(一)

    上一篇介绍了BeanFactory体系的所有接口——Spring源码分析——BeanFactory体系之接口详细分析,本篇就接着介绍BeanFactory体系的抽象类和接口. 一.BeanFactor ...

  7. 【spring源码分析】IOC容器初始化(总结)

    前言:在经过前面十二篇文章的分析,对bean的加载流程大致梳理清楚了.因为内容过多,因此需要进行一个小总结. 经过前面十二篇文章的漫长分析,终于将xml配置文件中的bean,转换成我们实际所需要的真正 ...

  8. 【spring源码分析】IOC容器初始化(七)

    前言:在[spring源码分析]IOC容器初始化(六)中分析了从单例缓存中加载bean对象,由于篇幅原因其核心函数 FactoryBeanRegistrySupport#getObjectFromFa ...

  9. spring源码分析系列

    spring源码分析系列 (1) spring拓展接口BeanFactoryPostProcessor.BeanDefinitionRegistryPostProcessor spring源码分析系列 ...

  10. Spring源码分析(十八)创建bean

    本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 目录 一.创建bean的实例 1. autowireConstructor 2 ...

随机推荐

  1. css-css和html的结合方式(四种结合方式)

    (1)在每个HTML标签上面都有一个属性 style,把css和HTML结合在一起   <div style="background-color:red;color:blue;&quo ...

  2. io流中read方法使用不当导致运行异常的一点

    public class CopyMp3test { public static void main(String[] args) throws IOException { FileInputStre ...

  3. Android 对话框 (AlertDialog)

    Android 提供了 AlertDialog 类可通过其内部类 Builder 轻松创建对话框窗口,但是没法对这个对话框窗口进行定制,为了修改 AlertDialog 窗口显示的外观,解决的办法就是 ...

  4. Fragment的setUserVisibleHint方法实现懒加载

    public abstract class LazyFragment extends Fragment {     protected boolean isVisible;     /**       ...

  5. 对象的数据属性(Object)

    value: 对象属性的默认值,默认值为undefined configurable: 能否使用delete.能否需改属性特性.或能否修改访问器属性.,false为不可重新定义,默认值为true en ...

  6. Promise 初步

    在JavaScript的世界中,所有代码都是单线程执行的. 由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行.异步执行可以用回调函数实现: function ca ...

  7. redis集群热扩展(基于4.0.9)

    1:环境说明,首先说一下要做的事情,我们要迁移redis集群槽位,现有redis集群环境如下 我们看一下集群的基本信息: > cluster nodes 8ea64a0049e0b193296a ...

  8. unwrapped与wrapped变量取值的问题

    unwrapped与wrapped变量取值的问题 当我们在定义一个tableView时,是可以使用3种定义方式的,第一种就是定义成optional(AnyObject?)形式,第二种为non-opti ...

  9. [翻译] PJR Signature View

    PJR Signature View https://github.com/paritsohraval100/PJRSignatureDemo It is a UIView subclass by w ...

  10. Gogs集成AD域LDAP

    操作步骤: 添加认证源 使用管理员账号登录Gogs,进入控制面板→认证源管理→添加新的源 设置如图所示 使用ldap认证源登录 配置成功后,登录时可选择认真源,界面如图所示