一、前沿
在 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 一致,故不需要做循环依赖检查
总结:三级缓存主要是为了解决依赖对象需要创建代理对象时的问题,如果依赖对象不需要创建代理对象的话则使用 一级 和 二级缓存即可