Spring 之循环依赖底层源码剖析

版本说明

本文使用的 Spring 版本为 5.2.8.RELEASE,在阅读 Spring 底层源码时,不同版本之间可能会有细微的差别。

循环依赖的概述

官方解释说明

翻译:如果主要使用构造方法注入,则可能会创建一个无法解决的循环依赖场景。例如:类 A 通过构造方法注入需要类 B 的实例,类 B 通过构造方法注入要求类 A 的实例。如果为类 A 和类 B 配置 Bean 以相互注入,那么 Spring IOC 容器会在运行时检测到这个循环引用,并抛出 BeanCurrentlyInCreationException 异常。一种可能的解决方案是编辑一些类的源代码,由 setter 而不是构造方法进行注入。或者,避免构造方法注入,只使用 setter 注入。换句话说,尽管不建议这样做,但您可以使用 setter 注入来配置循环依赖关系。与典型的情况(没有循环依赖关系)不同,Bean A 和 Bean B 之间的循环依赖关系迫使其中一个 Bean 在完全初始化之前注入另一个 Bean(典型的先有鸡后有蛋的场景)。

结论:对于 A、B、C 循环依赖的问题,只要 Bean A 的注入方式是 setter 且是单例,就不会发生循环依赖的问题。

什么是循环依赖

Spring 中的循环依赖指的是当两个或多个 Bean 之间存在相互依赖关系时,可能导致的循环引用问题。具体来说,当 Bean A 依赖于 Bean B,而 Bean B 又依赖于 Bean C,而 Bean C 又依赖于 Bean A,这样就会发生循环依赖。这种情况下,Spring 容器在初始化 Bean 的过程中可能会陷入死循环,导致应用程序无法启动或者抛出异常。这是因为 Spring 在创建 Bean 的过程中是通过构造方法注入或者 Setter 方法注入依赖的,如果存在循环依赖,那么容器就无法决定先创建哪个 Bean,从而无法满足依赖关系。

面试技巧

通常来说,如果面试问到 Spring 容器的内部是如何解决循环依赖的,那么一定是指在默认的单例 Bean 中,属性互相引用的场景。

循环依赖验证一

循环依赖的演示代码

这里将演示,在 Spring 中,使用构造方法注入会很容易产生循环依赖的问题。

  • 定义 Bean A
1
2
3
4
5
6
7
8
9
10
@Service
public class ServiceA {

private final ServiceB serviceB;

public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}

}
  • 定义 Bean B
1
2
3
4
5
6
7
8
9
10
@Service
public class ServiceB {

private final ServiceA serviceA;

public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}

}
  • 定义测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 这里基于 SpringBootTest 进行单元测试
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MainTestApplication.class})
public class MainTestApplication {

@Autowired
private ServiceA serviceA;

@Autowired
private ServiceB serviceB;

@Test
public void test() {

}

}
  • 代码执行输出的结果,会抛出 BeanCurrentlyInCreationException 异常
1
2
3
4
5
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'serviceA': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:219)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
  • 构造方法注入很容易产生循环依赖的问题,想让构造方法注入支持循环依赖,那是不可能的。如果构造方法注入支持循环依赖,那么程序员就可以无限套娃,代码如下
1
2
3
4
5
6
7
8
public class ClientConstructor {

public static void main(String[] args) {
// 无限套娃
new ServiceA(new ServiceB(new ServiceA(new ServiceB())));
}

}
  • 简而言之,多个类在各自实例化时都需要对方实例,这就类似于线程死锁,如果不采取一种办法解决,那么它们将永远互相等待下去

循环依赖的解决方案

在 Spring 中,可以使用 setter 注入且单例(Singleton)来解决循环依赖的问题。

  • 定义 Bean A
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class ServiceA {

private ServiceB serviceB;

public ServiceA() {

}

public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}

}
  • 定义 Bean B
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Service
public class ServiceB {

private ServiceA serviceA;

public ServiceB() {

}

public void setServiceA(ServiceA serviceA) {
this.serviceA = serviceA;
}

}

循环依赖验证二

这里将演示,在 Spring 中,使用 setter 注入且单例(Singleton)的场景是支持循环依赖的,而使用 setter 注入且原型(Prototype)的场景是不支持循环依赖的。

循环依赖的演示代码

  • 定义 A 类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class A {

private B b;

public B getB() {
return this.b;
}

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

public A() {
System.out.println("A created success");
}

}
  • 定义 B 类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class B {

private A a;

public A getA() {
return this.a;
}

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

public B() {
System.out.println("B created sucdess");
}

}
  • 定义测试类
1
2
3
4
5
6
7
8
9
public class SpringContainerTest {

public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
A a = context.getBean(A.class);
B b = context.getBean(B.class);
}

}
  • 定义 Spring 的 XML 配置文件,scope 的默认值就是 singleton,可以省略不写,表示每次从 IOC 容器中获取的 Bean 实例都是单例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="a" class="com.java.interview.spring.dependency2.A" scope="singleton">
<property name="b" ref="b"/>
</bean>

<bean id="b" class="com.java.interview.spring.dependency2.B" scope="singleton">
<property name="a" ref="a"/>
</bean>

</beans>
  • 代码执行输出的结果如下,使用 setter 注入且单例(Singleton)的场景是支持循环依赖的,程序运行不会报错
1
2
A created success
B created sucdess
  • 将上述 Spring 的 XML 配置文件中的 scope 修改为 prototype,表示每次从 IOC 容器中获取 Bean 实例时,都会产生一个新的 Bean 实例
1
2
3
4
5
6
7
<bean id="a" class="com.java.interview.spring.dependency2.A" scope="prototype">
<property name="b" ref="b"/>
</bean>

<bean id="b" class="com.java.interview.spring.dependency2.B" scope="prototype">
<property name="a" ref="a"/>
</bean>
  • 代码执行就会报下面的错误,根本原因是使用 setter 注入且原型(Prototype)的场景是不支持循环依赖的
1
2
3
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'a' defined in class path resource [application-context.xml]: Cannot resolve reference to bean 'b' while setting bean property 'b'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'b' defined in class path resource [application-context.xml]: Cannot resolve reference to bean 'a' while setting bean property 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:342)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:113)

循环依赖的解决方案

Spring 内部是通过三级缓存来解决循环依赖,底层源码如下:

  • 第一级缓存

    • Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    • 可称之为成品单例池,存放已经经历了完整生命周期的 Bean 对象,常说的 Spring 容器就是指它,平时获取单例 Bean 就是在这里面获取的
  • 第二级缓存

    • Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    • 存放早期暴露出来的 Bean 对象,Bean 的生命周期未结束(属性还未填充完整,可以认为是半成品的 Bean 对象)
  • 第三级缓存

    • Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    • 存放可以生成 Bean 的工厂,用于生产(创建)Bean 对象

特别注意

只有单例的 Bean 会通过三级缓存提前暴露来解决循环依赖的问题,而非单例的 Bean,每次从容器中获取都是一个全新的 Bean,即都会重新创建 Bean,所以非单例的 Bean 是没有缓存的,Spring 不会将其放到三级缓存中,这也导致了使用 setter 注入且原型(Prototype)的场景不支持循环依赖。

循环依赖的底层源码剖析

源码 Debug 的前置知识

实例化与初始化

  • 实例化:在堆内存中申请一块内存空间,用于创建(存放)对象

  • 初始化:完成对象属性的填充

三级缓存与四大方法

三级缓存

Spring 内部是通过三级缓存来解决循环依赖,底层源码如下:

  • 第一级缓存

    • Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    • 存放的是已经初始化好了的 Bean 实例,Bean 名称与 Bean 实例相对应,即所谓的单例池。
    • 表示 Bean 已经经历了完整的生命周期。
  • 第二级缓存

    • Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    • 存放的是实例化了,但是未初始化的 Bean 实例,Bean 名称与 Bean 实例相对应。
    • 表示 Bean 的生命周期还没走完(属性还未填充完整,可以认为是半成品的 Bean 对象)就将这个 Bean 放入该缓存中,也就是将已实例化但未初始化的 Bean 放入该缓存中。
  • 第三级缓存

    • Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    • 存放的是生成 Bean 的 Bean 工厂实例,Bean 名称与 Bean 工厂实例对应。
    • 假如 A 类实现了 FactoryBean 接口,那么依赖注入的不是 A 类,而是 A 类生成的 Bean 实例。
四大方法
  • getSingleton():从容器里面获得单例的 Bean,如果不存在,则会创建 Bean。
  • doCreateBean():执行创建 Bean 的操作(在 Spring 中以 do 开头的方法都是干实事的方法)。
  • populateBean():创建完 Bean 之后,对 Bean 的属性进行填充。
  • addSingleton():Bean 初始化完成之后,将其添加到单例池中,下次执行 getSingleton() 方法时就能获取到单例的 Bean。

对象在三级缓存中的迁移

  • (1) A 在初始化的过程中需要 B,于是 A 将自己放到第三级缓存里面,然后去实例化 B。

  • (2) B 实例化的时候发现需要 A,于是 B 先查第一级缓存,发现缓存里没有;再查第二级缓存,缓存里还是没有;再查第三级缓存,终于找到了 A;然后将第三级缓存里面的 A 放到第二级缓存里面,并删除第三级缓存里面的 A。

  • (3) B 顺利完成初始化后,将自己放到第一级缓存里面(此时 B 里面的 A 依然是处于初始化中状态),然后回来接着初始化 A。

  • (4) 由于 B 已经初始化完成,A 可以直接从第一级缓存里面获取到 B,然后完成 A 的初始化,并将 A 放到第一级缓存里面,并删除第二级缓存里面的 A。

源码 Debug 的详细分析

Debug 的代码

这里将基于上面 循环依赖验证二 的源码进行 Debug 分析。

Debug 的步骤

如何阅读框架的源码?

阅读框架源码的方法是:打断点 + 看输出的日志。

第一阶段

ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml") 代码处打上断点,逐步执行 Step Over,发现执行 new ClassPathXmlApplicationContext() 操作时,Bean A 和 Bean B 都已经被创建好了,因此需要进入该操作的内部。

执行 Step Into,进入 new ClassPathXmlApplicationContext() 操作的内部,首先进入了静态代码块,这里没有要看的代码,执行 Step Out 跳出该代码块。

再次执行 Step Into,进入 ClassPathXmlApplicationContext 类的构造方法,该构造方法使用 this 调用了另一个重载构造方法。

继续执行 Step Into,进入 ClassPathXmlApplicationContext 的重载构造方法后,单步执行 Step Over,发现执行完 refresh() 方法后会输出日志,于是将断点打在 refresh() 那一行代码。

继续执行 Step Into,进入 refresh() 方法,发现执行完 finishBeanFactoryInitialization() 方法后会输出日志,于是将断点打在 finishBeanFactoryInitialization() 那一行代码,从注释也可以看出该方法完成了非懒加载单例 Bean 的实例化。

继续执行 Step Into,进入 finishBeanFactoryInitialization() 方法,发现执行完 beanFactory.preInstantiateSingletons() 方法后会输出日志,于是将断点打在 beanFactory.preInstantiateSingletons() 那一行代码,,从注释也可以看出该方法完成了非懒加载单例 Bean 的实例化。

第二阶段

执行 Step Into,进入 beanFactory.preInstantiateSingletons() 方法,发现执行完 getBean() 方法后会输出日志,于是将断点打在 getBean() 那一行代码。

执行 Step Into,进入 getBean() 方法,发现调用了 doGetBean() 方法,也就是前面说过的:在 Spring 里面,以 do 开头的方法都是干实事的方法。

执行 Step Into,进入 doGetBean() 方法,这里的 transformedBeanName() 方法会将用户定义的别名转换为 Bean 的真实名称。

执行 Step Over,继续执行后面的 getSingleton() 方法

执行 Step Into,进入 getSingleton() 方法,发现调用了其重载的方法。

执行 Step Into,进入重载的 getSingleton() 方法,传入的 allowEarlyReference 参数,表示是否可以从第二级缓存中获取 Bean。发现该方法会尝试从第一级缓存 singletonObjects 中获取 Bean A,由于 Bean A 现在还没有开始创建,因此从第一级缓存中获取不到,而且 isSingletonCurrentlyInCreation() 方法会返回 false,导致最后返回的 Bean A 为 null

返回到 doGetBean() 方法中,执行完 getSingleton() 方法会返回 null

执行 Step Over,继续执行后面的代码,可以看到 RootBeanDefinition 实例。在 XML 配置文件中定义的 Bean,对于 Spring 来说就是一个个的 RootBeanDefinition 实例。

执行 Step Over,继续执行后面的代码,可以看到有一个 dependsOn 变量,它对应于 Bean 的 depends-on 属性,因为在 XML 配置文件中没有配置过该属性,因此为 null

执行 Step Over,继续执行后面的代码,终于看到开始准备创建 Bean A 了。

第三阶段

执行 Step Into,进入 getSingleton() 方法中,在 IDEA 2020 里面需要使用鼠标左键点击 getSingleton() 方法进入。

getSingleton() 方法中,首先从第一级缓存 singletonObjects 获取 Bean A,缓存里面没有,那么就需要创建 Bean A,此时日志会输出 Creating shared instance of singleton bean 'a'

当执行完 singletonObject = singletonFactory.getObject() 时,日常会输出 A created success,这说明执行 singletonFactory.getObject() 方法时将会实例化 Bean A,并且根据变量名可得知是单例工厂创建的,这个单例工厂就是传入的 Lambda 表达式。

执行 Step Into,进入 createBean() 方法,mbdToUse 将用于创建 Bean A。

执行 Step Over,继续执行后面的代码,终于要执行 doCreateBean() 方法实例化 Bean A 了。

执行 Step Into,进入 doCreateBean() 方法,在 factoryBeanInstanceCache 中并不存在 Bean A 对应的 Wrapper 缓存,因此要先创建 Bean A 对应的 instanceWrapper。形象的理解:Bean A 的实例化需要经过 instanceWrapper 之手,Bean A 实例被 instanceWrapper 包裹在其中。

执行 Step Into,进入 createBeanInstance() 方法,里面的核心逻辑是使用反射创建 Bean A。

执行 Step Over,继续执行后面的代码,有一个 resolved 变量,写着注释:Shortcut when re-creating the same bean…,如果值为 true,表示使用快捷方式重新创建同一个 Bean。

执行 Step Over,继续执行后面的 instantiateBean() 方法。

执行 Step Into,进入 instantiateBean() 方法,会执行 getInstantiationStrategy().instantiate() 方法完成 Bean A 的实例化。

执行 Step Into,进入 getInstantiationStrategy().instantiate() 方法,首先通过执行 bd.resolvedConstructorOrFactoryMethod 来获取已经解析好的构造器 ,由于是第一次创建,所以获取不到构造器,因此 constructorToUse == null;然后获取 Bean A 的类型,如果发现是接口,则直接抛出异常;最后通过执行 clazz.getDeclaredConstructor() 方法来获取 Bean A 的构造器,并赋值给 constructorToUse 变量。

执行 Step Over,继续执行后面的代码,可以看到上面获取构造器 constructorToUse 的目的是为了实例化 Bean A。

执行 Step Into,进入 BeanUtils.instantiateClass() 方法,日志会输出 A created success

执行 Step Over,会返回到 getInstantiationStrategy().instantiate() 方法中,开始创建 Bean A 实例,不过还没有进行初始化,可以看到属性 b = null

执行 Step Over,会返回到 instantiateBean() 方法中,得到刚刚创建的 Bean A 实例,但其属性 b 并未被初始化。

执行 Step Over,将已实例化的 Bean A 封装进 BeanWrapper 中,并返回 BeanWrapper 实例。

执行 Step Over,会返回到 createBeanInstance() 方法中,得到刚刚创建的 BeanWrapper 实例,该 BeanWrapper 封装了创建好的 Bean A 实例。

执行 Step Over,会返回到 doCreateBean() 方法中,获得 BeanWrapper 实例,并通过 BeanWrapper 实例获取创建好的 Bean A 实例。

执行 Step Over,继续执行后面的代码,获取 Bean A 的全类名。

执行 Step Over,继续执行后面的代码,执行 BeanPostProcessor。

执行 Step Over,继续执行后面的代码,如果该 Bean 是单例,并且允许循环依赖,且当前 Bean 正在创建过程中,那么就允许提前暴露该 Bean,也就是将该 Bean 放到第三级缓存 singletonFactories 中。

执行 Step Into,进入 addSingletonFactory() 方法。首先去第一级缓存 singletonObjects 中找一下有没有 Bean A,如果找不到,则将 Bean A 添加到第三级缓存 singletonFactories 中,并将 Bean A 从第二级缓存 earlySingletonObjects 中移除。最后将 beanName 添加至 registeredSingletons 中,表示该单例 Bean 已经被注册过。

第四阶段

执行 Steop Over,会返回到 doCreateBean() 方法中,需要执行 populateBean() 方法对 Bean A 中的属性进行填充。

执行 Step Into,进入 populateBean() 方法,首先会获取 Bean A 的属性列表。

执行 Steop Over,执行后面的 applyPropertyValues() 方法,完成 Bean A 属性的填充。

执行 Step Into,进入 applyPropertyValues() 方法,获取到 Bean A 的属性列表,发现里面有个属性为 b

执行 Step Over,继续执行后面的代码,遍历每一个属性,并对每一个属性进行注入,valueResolver.resolveValueIfNecessary() 方法的作用是:给定一个 PropertyValue,返回一个值,如有必要,解析工厂中对其他 Bean 的任何引用。

执行 Step Into,进入 valueResolver.resolveValueIfNecessary() 方法,会执行 resolveReference() 方法来解决依赖注入的问题。

执行 Step Into,进入 resolveReference() 方法,首先获得属性 b 的名称,再通过 this.beanFactory.getBean() 方法获取 Bean B 的实例。

执行 Step Into,进入 this.beanFactory.getBean() 方法,这里会调用熟悉的 doGetBean() 方法。

执行 Step Into,进入 doGetBean() 方法,由于 Bean B 还没有实例化,因此 getSingleton() 方法返回 null

执行 Step Over,继续执行后面的代码,又回到这个熟悉的地方,尝试获取 Bean B 实例,获取不到就创建。

执行 Step Into,进入 getSingleton() 方法,首先尝试从一级缓存 singletonObjects 中获取 Bean B 实例,缓存里面没有,因此获取不到。

执行 Step Over,继续执行后面的 singletonFactory.getObject() 方法创建 Bean B 实例。

执行 Step Over,继续执行 createBean() 方法创建 Bean B 实例。

执行 Step Into,进入 createBean() 方法,首先会获取 Bean B 的类型。

执行 Step Over,继续执行后面的 doCreateBean() 方法。

执行 Step Into,进入 doCreateBean() 方法,会通过 createBeanInstance() 方法创建 Bean B 对应的 BeanWrapper 实例。

执行 Step Into,进入 createBeanInstance() 方法,会执行 instantiateBean() 方法创建 Bean B 对应的 BeanWrapper 实例。

执行 Step Into,进入 instantiateBean() 方法,会执行 getInstantiationStrategy().instantiate() 方法创建 Bean B 实例。

执行 Step Into,进入 getInstantiationStrategy().instantiate() 方法,会先获取 Bean B 的构造器,然后再执行 BeanUtils.instantiateClass() 方法创建 Bean B 实例。

执行 Step Into,进入 BeanUtils.instantiateClass(constructorToUse) 方法,通过调用 B 类的构造器创建 Bean B 实例,此时日志会输出:B created success

执行 Step Over,会返回到 instantiateBean() 方法中,将创建好的 Bean B 实例封装进 BeanWrapper 实例。

执行 Step Over,会返回到 doCreateBean() 方法中,createBeanInstance() 方法返回了封装着 Bean B 实例的 BeanWrapper。

执行 Step Over,继续执行后面的代码,执行 BeanPostProcessor 的处理过程。

执行 Step Over,继续执行后面的代码,由于 Bean B 满足单例并且正在被创建,因此 Bean B 可以被提前暴露出去(在属性还未初始化的时候可以提前暴露出去),于是执行 addSingletonFactory() 方法将其添加到第三级缓存 singletonFactory 中。

执行 Step Into,进入 addSingletonFactory() 方法,可以看到会将 Bean B 实例添加到第三级缓存 singletonFactory 中,并从二级缓存 earlySingletonObjects 中移除 Bean B,同时注册其 beanName

执行 Step Over,返回到 doCreateBean() 方法中,执行 populateBean() 方法填充 Bean B 的属性。

第五阶段