Spring 4.x 源码系列6-循环依赖

一、前沿

在 Spring 的 bean创建 文章中我们了解了bean创建的复杂过程,那篇文章中也讲了一些关于循环依赖的问题,循环依赖本是一个死循环无解难题,本文将通过demo示例来具体为大家分析 Spring 是如何解决这一无解难题的

二、循环依赖定义

循环依赖就是循环引用,也就是两个或者两个以上的 bean 相互之间的持有对方,比如说 A 引用 B,B 引用 C,C 引用 A,则它们之间相互引用形成一个环。这里要和循环调用区分开,循环调用是方法之间的循环调用,循环调用是无法解决的,除非有终结条件,否则就是死循环,循环调用的结果是最终会导致内存溢出

三、Spring 解决循环依赖

Spring 容器中循环依赖有 构造器依赖setter 循环依赖 两种,下面通过示例分别介绍

3.1 构造器依赖(Spring无法解决

表示通过构造器注入构成的循环依赖,此依赖Spring是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖。原因是 Spring 在创建bean实例 BeanWrapper 时根本没有缓存正在创建的bean

3.1.1 demo示例

实体类代码如下:

import org.springframework.beans.factory.InitializingBean;

/**
 * A测试
 *
 * @date 2019-12-30 11:18
 **/
public class BeanATest implements InitializingBean {
    private BeanBTest b;

    public BeanATest() {
        System.out.println("BeanATest 创建开始");
    }

    public BeanATest(BeanBTest b) {
        System.out.println("BeanATest 创建开始,依赖 BeanBTest 传入");
        this.b = b;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BeanATest 创建结束,依赖 BeanBTest " + b);
    }
}


import org.springframework.beans.factory.InitializingBean;

/**
 * B测试
 *
 * @date 2019-12-30 11:18
 **/
public class BeanBTest implements InitializingBean {
    private BeanCTest c;

    public BeanBTest() {
        System.out.println("BeanBTest 创建开始");
    }

    public BeanBTest(BeanCTest c) {
        System.out.println("BeanBTest 创建开始,依赖 BeanCTest传入");
        this.c = c;
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BeanBTest 创建结束,依赖 BeanCTest " + c);
    }
}


import org.springframework.beans.factory.InitializingBean;

/**
 * C测试
 *
 * @date 2019-12-30 11:18
 **/
public class BeanCTest implements InitializingBean {
    private BeanATest a;

    public BeanCTest() {
        System.out.println("BeanCTest 创建开始");
    }

    public BeanCTest(BeanATest a) {
        System.out.println("BeanCTest 创建开始,依赖 BeanATest 传入");
        this.a = a;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BeanCTest 创建结束,依赖 BeanATest " + a);
    }
}

resources 目录下的配置文件 application_circle.xml 内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>
	<!-- BeanATest -->
	<bean id="beanATest" class="com.springboot.demo.bean.circle.BeanATest">
		<constructor-arg index="0" ref="beanBTest"/>
	</bean>

	<!-- BeanBTest -->
	<bean id="beanBTest" class="com.springboot.demo.bean.circle.BeanBTest">
		<constructor-arg index="0" ref="beanCTest"/>
	</bean>

	<!-- BeanCTest -->
	<bean id="beanCTest" class="com.springboot.demo.bean.circle.BeanCTest">
		<constructor-arg index="0" ref="beanATest"/>
	</bean>
</beans>

测试类代码如下:

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * ApplicationContext测试
 *
 * @date 2019-12-24 11:31
 **/
public class ApplicationContextTest {
    public static void main(String[] args) {
        System.out.println("开始初始化ApplicationContext容器");
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application_circle.xml");
        System.out.println("ApplicationContext容器初始化完毕");
        applicationContext.close();
    }
}

运行结果如下:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanATest': Requested bean is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:367)
	... 37 more

结论:Spring 对于构造器的循环依赖直接抛出异常,是无法解决的

3.2 setter 循环依赖(Spring可以解决

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

	// DefaultSingletonBeanRegistry 的 addSingletonFactory 方法
	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
		Assert.notNull(singletonFactory, "Singleton factory must not be null");
		synchronized (this.singletonObjects) {
			// beanName 对应的 bean 没有被缓存
			if (!this.singletonObjects.containsKey(beanName)) {
				// 增加 beanName 对应的 ObjectFactory 缓存
				this.singletonFactories.put(beanName, singletonFactory);
				// 移除 beanName 对应的早期缓存
				this.earlySingletonObjects.remove(beanName);
				// registeredSingletons 也增加 beanName 对应的缓存
				this.registeredSingletons.add(beanName);
			}
		}
	}

3.2.1 demo示例

实体类代码如下:

import org.springframework.beans.factory.InitializingBean;

/**
 * A测试
 *
 * @date 2019-12-30 11:18
 **/
public class BeanATest implements InitializingBean {
    private BeanBTest b;

    public BeanATest() {
        System.out.println("BeanATest 创建开始");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BeanATest 创建结束,依赖 BeanBTest " + b);
    }

    public BeanBTest getB() {
        return b;
    }

    public void setB(BeanBTest b) {
        this.b = b;
    }
}


import org.springframework.beans.factory.InitializingBean;

/**
 * B测试
 *
 * @date 2019-12-30 11:18
 **/
public class BeanBTest implements InitializingBean {
    private BeanCTest c;

    public BeanBTest() {
        System.out.println("BeanBTest 创建开始");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BeanBTest 创建结束,依赖 BeanCTest " + c);
    }

    public BeanCTest getC() {
        return c;
    }

    public void setC(BeanCTest c) {
        this.c = c;
    }
}


import org.springframework.beans.factory.InitializingBean;

/**
 * C测试
 *
 * @date 2019-12-30 11:18
 **/
public class BeanCTest implements InitializingBean {
    private BeanATest a;

    public BeanCTest() {
        System.out.println("BeanCTest 创建开始");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("BeanCTest 创建结束,依赖 BeanATest " + a);
    }

    public BeanATest getA() {
        return a;
    }

    public void setA(BeanATest a) {
        this.a = a;
    }
}

resources 目录下的配置文件 application_circle.xml 内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>
	<!-- BeanATest -->
	<bean id="beanATest" class="com.springboot.demo.bean.circle.BeanATest">
		<property name="b" ref="beanBTest"/>
	</bean>

	<!-- BeanBTest -->
	<bean id="beanBTest" class="com.springboot.demo.bean.circle.BeanBTest">
		<property name="c" ref="beanCTest"/>
	</bean>

	<!-- BeanCTest -->
	<bean id="beanCTest" class="com.springboot.demo.bean.circle.BeanCTest">
		<property name="a" ref="beanATest"/>
	</bean>
</beans>

测试类代码不变,运行结果如下:

开始初始化ApplicationContext容器
BeanATest 创建开始
BeanBTest 创建开始
BeanCTest 创建开始
BeanCTest 创建结束,依赖 BeanATest com.springboot.demo.bean.circle.BeanATest@a1153bc
BeanBTest 创建结束,依赖 BeanCTest com.springboot.demo.bean.circle.BeanCTest@55141def
BeanATest 创建结束,依赖 BeanBTest com.springboot.demo.bean.circle.BeanBTest@55182842
ApplicationContext容器初始化完毕

创建bean步骤如下:

1)、Spring容器创建单例 BeanATest,首先根据无参构造器创建 BeanATest,并暴露一个 ObjectFactory 用于返回一个提前创建中的 bean,并将 beanATest 与 ObejctFactory 的对应关系缓存到 singletonFactories 对象中,然后执行 setB 方法注入 BeanBTest

2)、Spring容器创建单例 BeanBTest,首先根据无参构造器创建 BeanBTest,并暴露一个 ObjectFactory 用于返回一个提前创建中的 bean,并将 beanBTest 与 ObejctFactory 的对应关系缓存到 singletonFactories 对象中,然后执行 setC 方法注入 BeanCTest

3)、Spring容器创建单例 BeanCTest,首先根据无参构造器创建 BeanCTest,并暴露一个 ObjectFactory 用于返回一个提前创建中的 bean,并将 beanCTest 与 ObejctFactory 的对应关系缓存到 singletonFactories 对象中,然后执行 setA 方法注入 BeanATest,注入 BeanATest 时由于提前暴露了对象工厂 ObjectFactory(singletonFactories 缓存中已经缓存了 BeanATest 和 ObjectFactory 的映射关系),调用 ObjectFactory 的 getObject 方法直接返回一个创建中的 BeanATest

4)、BeanCTest 创建完成,依赖注入 BeanCTest 后 BeanBTest 创建完成,最后依赖注入 BeanBTest 后 BeanATest 创建完成

结论:Spring 容器可以解决 Singleton bean 的 setter 循环依赖问题

对于 Singleton Bean 可以通过设置 ApplicationContext 的 setAllowCircularReferences 方法决定是否允许循环依赖

3.3 Prototype Bean 循环依赖(Spring无法解决

对于 Prototype 作用域的 Bean,Spring容器无法完成依赖注入,原因是 Spring 容器没有缓存 Prototype 作用域的 Bean,因此无法提前暴露一个创建中的 bean

由于 Spring 无法解决 构造器循环依赖问题,所以我们只需要示例演示 Prototype 作用域的 Bean 的 setter 循环依赖,看看 Spring 能否解决 Prototype Bean 的循环依赖问题

3.3.1 demo示例

实体类代码 3.2 中的一致

resources 目录下的配置文件 application_circle.xml 即可,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "https://www.springframework.org/dtd/spring-beans-2.0.dtd">

<beans>
	<!-- BeanATest -->
	<bean id="beanATest" class="com.springboot.demo.bean.circle.BeanATest" scope="prototype">
		<property name="b" ref="beanBTest"/>
	</bean>

	<!-- BeanBTest -->
	<bean id="beanBTest" class="com.springboot.demo.bean.circle.BeanBTest" scope="prototype">
		<property name="c" ref="beanCTest"/>
	</bean>

	<!-- BeanCTest -->
	<bean id="beanCTest" class="com.springboot.demo.bean.circle.BeanCTest" scope="prototype">
		<property name="a" ref="beanATest"/>
	</bean>
</beans>

测试类代码如下:

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * ApplicationContext测试
 *
 * @date 2019-12-24 11:31
 **/
public class ApplicationContextTest {
    public static void main(String[] args) {
        System.out.println("开始初始化ApplicationContext容器");
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application_circle.xml");
        System.out.println("ApplicationContext容器初始化完毕");
        // ApplicationContext 启动时只会创建非延迟加载的 Singleton Bean,而 Prototype Bean 是延迟加载,需要手动调用
        applicationContext.getBean("beanATest");
        applicationContext.close();
    }
}

运行结果如下:

结论:Spring 容器无法解决 Prototype Bean 的循环依赖问题

3.4 三级缓存的必要性

A 中引用B,B 中引用A

3.4.1 去除三级缓存后 A B 对象创建步骤如下:

1)A 实例化时,创建A@1952  ,加入二级缓存  A@1952

2)A 属性填充时,创建B对象 B@2449 , 加入二级缓存  B@2449

3)B 属性填充时,二级缓存获取到 A@1952,此时 b 的 a属性已经是半成品

4)B 方法初始化时,产生 cglib B@3349,代理B中没有a属性值

5)cglib B@3349 放入一级缓存中, 二级缓存中移除 B@2449 

6)A 属性填充时返回代理 cglib B@3349,此时 A@1952 中b属性是 cglib B@3349

7)A 方法初始化时,产生 cglib A@3849对象,代理A中没有b属性值,此时 exposedObject != bean 对象了

8)检查A( cglib A@3849)中所依赖的对象B还没有创建好(此时A依赖的所有属性都应该已经创建好),此时抛出异常

结论:去除三级缓存后,A属性填充后产生的 A@1952 对象和 A 方法初始化后产生的代理对象 cglib A@3849 不一致,进行循环依赖检查时发现 A 中 B 属性还没有实例化好,则抛出了异常 

3.4.2  保留三级缓存后 A B 对象创建步骤如下:

1)A 实例化时创建 A@1917 ,加入三级缓存 lambda 表达式

2)A 属性填充时,创建B对象 B@2260   加入三级缓存  lambda 表达式

3)B 属性填充时,使用三级缓存调用 getObject 方法获取到代理 cglib A@2684,cglib A@2684加入二级缓存,三级缓存删除A,此时 b 的 a属性是代理对象,已经是半成品

4)B 方法初始化时,产生 cglib B@2915,代理B中没有a属性值

5)B对象 cglib B@2915 放入一级缓存中, 三级缓存中移除 b

6)A 属性填充时返回代理 cglib B@2915,此时 A@1917 中b属性是 cglib B@2915

7)A 方法初始化时,earlyProxyReferences 中的对象还是A@1917对象,故没有产生A代理对象,此时 exposedObject == bean,将代理对象 cglib A@2684 赋值给 exposedObject,不用做循环依赖检查

结论:保留三级缓存后,A属性填充后产生的 A@1917 对象和 A 方法初始化后对象 A 一致,故不需要做循环依赖检查

总结:三级缓存主要是为了解决依赖对象需要创建代理对象时的问题,如果依赖对象不需要创建代理对象的话则使用 一级 和 二级缓存即可

猜你喜欢

转载自blog.csdn.net/ywlmsm1224811/article/details/103765094