Spring 解决循环依赖原理

循环依赖:是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用

  • 自己依赖自己的直接依赖

1.png

  • 两个对象之间的直接依赖

2.png

  • 多个对象之间的间接依赖

3.png

前面两种情况的直接循环依赖比较直观,非常好识别,但是第三种间接循环依赖的情况有时候因为业务代码调用层级很深,不容易识别出来

循环依赖场景

4.png

单例的setter注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
public class TestService1 {

@Autowired
private TestService2 testService2;

public void test1() {
}
}

@Service
public class TestService2 {

@Autowired
private TestService1 testService1;

public void test2() {
}
}

这是一个经典的循环依赖,但是它能正常运行,得益于spring的内部机制,让我们根本无法感知它有问题,因为spring默默帮我们解决了,将实例化与初始化步骤分开,在中间过程中给其他对象赋值的时候,并不是一个完整对象,而是把半成品对象赋值给了其他对象

Bean创建前有一个集合singletonsCurrentlyInCreation,用于标记正在创建这个状态

  • 一级缓存:为“Spring 的单例属性”而生,就是个单例池,用来存放已经初始化完成的单例 Bean

  • 二级缓存:为“解决 AOP”而生,存放的是半成品的 AOP 的单例 Bean

  • 三级缓存:为“打破循环”而生,存放的是生成半成品单例 Bean 的工厂方法,SingletonFactories是个函数式接口,lambda表达式

spring内部有三级缓存

  • singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
  • earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
  • singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象。

实例化bean之前先标记为创建状态

img

  1. 先实例化testService1doCreateBean,发现是创建状态,三级缓存存入lambda表达式,这个表达式是生成bean的代理对象的,如果是AOP则反射对象,否则bean自身,是个半成品,添加A的代理对象到3级缓存
  2. 走到populateBean方法中,填充属性通过AutowiredAnnotationBeanPostProcesspostProcessProperties() 方法发现依赖testService2,继续递归testService2,重复1步骤
  3. 发现testService2 依赖 testService1,再解决testService2的依赖
  4. 发现testService1现在在3级缓存已经有了,把testService1从3级缓存删除,存到2级缓存,此时三级缓存存testService2,二级缓存存testService1
  5. 继续返回第二层,testService2填充了testService1的属性,从创建状态删除,把testService2添加到一级缓存,删除二级缓存
  6. 再返回第一层,testService1填充了testService2的属性,从创建状态删除,把testService1添加到一级缓存,删除二级缓存

源码分析

DefaultSingletonBeanRegistry#getSingleton(String) 获取单例 Bean 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public Object getSingleton(String beanName) {
// allowEarlyReference 允许早期依赖
return getSingleton(beanName, true);
}
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {

Object singletonObject = this.singletonObjects.get(beanName);
// 这个bean 正处于 创建阶段
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 并发控制
synchronized (this.singletonObjects) {
// 单例缓存是否存在
singletonObject = this.earlySingletonObjects.get(beanName);
// 是否运行获取 bean factory 创建出的 bean
if (singletonObject == null && allowEarlyReference) {
// 获取缓存中的 ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 将对象缓存到 earlySingletonObject中
this.earlySingletonObjects.put(beanName, singletonObject);
// 从工厂缓冲中移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}

上面的代码就是 Spring 尝试从缓存中加载单例。单例在 Spring 的同一个容器中只会被创建一次,后续再获取 bean,就直接从缓存中取了

补充一些方法和参数

  • isSingletonCurrentlyInCreation():判断当前单例bean是否正在建立中,也就是没有初始化完成(好比A的构造器依赖了B对象因此得先去建立B对象, 或则在A的populateBean过程当中依赖了B对象,得先去建立B对象,这时的A就是处于建立中的状态。)
  • allowEarlyReference :是否容许从singletonFactories中经过getObject拿到对象

先认识下 DefaultSingletonBeanRegistry 这个类里面的成员变量

  • Map<String, Object> singletonObjects key 就是 beanName ,value 就是 bean 实例
  • Map<String, ObjectFactory<?>> singletonFactories key 为 beanName,value 为创建 bean 的工厂
  • Map<String, Object> earlySingletonObjects key 为 beanName ,value 为 bean。但是和 singletonObjects 不同的是,bean 被加入到 earlySingletonObjects 的时候、这个 bean 还是处于一种创建中的状态,目的也很简单、Spring 用来解决某些场景下的循环依赖

Spring获取一个bean的简单过程:

  • 首先从singletonObjects获取,也就是单例IoC容器中
  • 然后从earlySingletonObjects中获取,也就是通过ObjectFactory实现的提前曝光的容器
  • 最后从singletonFactories获取,也就是实例化bean的实例工厂
  • 如果都获取不到,则新创建一个bean对象,也就说走上面分析的流程

    从缓存中获取的bean的过程,一般都称为三级缓存,前面三个步骤对应了1,2,3级缓存。接下来举一个案例分析这个过程,假如有bean的依赖关系为:A->B->C->A,当然这些都是基于属性依赖的,当A执行到populateBean方法实现属性注入的时候,会先去获取B实例,然后B执行populateBean会获取C实例,C执行到populateBean获取查找A实例,此时A实例正在被创建,又会循环上述过程,产生了循环依赖问题。Spring获取getBean()最终调用下面简化后的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
//关注点1
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
try {
if (mbd.isSingleton()) {
//关注点2
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
..............
}
return (T) bean;
}

​ 当A去查找bean的实例的时候,会调用上面的doGetBean方法获取,这个方法里面有两个关注点,分别是两个重载方法getSingleton,首先当A执行populateBean查找B的实例时调用第一个重载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);
}

    这个方法是从三级缓存中查找bean,第一级缓存singletonObjects里面放置的是实例化好的单例对象。第二级earlySingletonObjects里面存放的是提前曝光的单例对象(没有完全装配好)。第三级singletonFactories里面存放的是要被实例化的对象的对象工厂,由于B第一次获取还没有被创建,所以一级缓存singletonObjects获取结果肯定为null,再看看看看进入二级缓存中的条件isSingletonCurrentlyInCreation(beanName)

1
2
3
4
public boolean isSingletonCurrentlyInCreation(String beanName) {
//在这里表示bean是否正在创建的过程,此时B 尚未在创建中,所以会返回false
return this.singletonsCurrentlyInCreation.contains(beanName);
}

    上面的步骤中并没有任何操作往isSingletonCurrentlyInCreation中加入B的beanName的操作,所以压根不会进入二级缓存,直接就返回null了,然后就判断bean是否时单例的,如果时调用getSingleton(String beanName, ObjectFactory objectFactory),此时objectFactory时一个匿名内部类,实例B的获取是通过内部类的createBean获取的,这个是我们关注点2 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized (this.singletonObjects) {
//从一级缓存中获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,"不能从销毁的bean中创建");
}
//在这里将B的beanName添加到isSingletonCurrentlyInCreation
beforeSingletonCreation(beanName);
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<Exception>();
}
try {
//最终调用匿名内部类创建bean
singletonObject = singletonFactory.getObject();
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}

    这个方法首先会从一级缓存中查找B,很明显,查到的结果为null,然后调用beforeSingletonCreation(beanName),将B的beanName添加到singletonsCurrentlyInCreation中,也就是关注点1中无法进入二级缓存的那个集合校验:

1
2
3
4
5
6
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) &&
!this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}

    紧接着就会调用singletonFactory.getObject()创建名,也就是通过匿名内部类的createBean方法创建,前面分析过,创建bean最终会调用doCreateBean方法,这个方法简化了, 只看最核心的关注点3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
...........代码省略...........
//关注点3
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>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
//初始化和实例化bean
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
catch (Throwable ex) {
throw ex;
}
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
}
}
}
...........代码省略...........
return exposedObject;
}

    createBeanInstance利用反射创建了对象,下面我们看看关注点3earlySingletonExposure属性值的判断,其中有一个判断点就是isSingletonCurrentlyInCreation(beanName)

1
2
3
public boolean isSingletonCurrentlyInCreation(String beanName) {
return this.singletonsCurrentlyInCreation.contains(beanName);
}

    发现使用的是singletonsCurrentlyInCreation这个集合,在上面的步骤中将的B的BeanName已经填充进去了,所以可以查到,而且在初始化bean的时候,还会判断检查bean是否有循环依赖,而且是否允许循环依赖,这里的ABC形成了循环依赖,所以最终earlySingletonExposure结合其他的条件综合判断为true,进行下面的流程addSingletonFactory,这里是为这个Bean添加ObjectFactory,这个BeanName(A)对应的对象工厂,他的getObject方法的实现是通过getEarlyBeanReference这个方法实现的。首先我们看下addSingletonFactory的实现

1
2
3
4
5
6
7
8
9
10
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}

    往三级缓存singletonFactories存放数据,清除二级缓存中beanName对应的数据。这里有个很重要的点,是往三级缓存里面存入了值,这是Spring处理循环依赖的核心点。getEarlyBeanReference这个方法是getObject的实现,可以简单认为是返回了一个为填充完毕的A的对象实例。设置完三级缓存后,就开始了填充A对象属性的过程

    上面理清之后整体来分析以下ABC的初始化流程,当设置A的属性时,发现需要B类型的Bean,于是继续调用getBean方法创建,这次的流程和上面A的完全一致,然后到了填充C类型的Bean的过程,同样的调用getBean(C)来执行,同样到了填充属性A的时候,调用了getBean(A),我们从这里继续说,调用了doGetBean中的`Object sharedInstance = getSingleton(beanName),还是关注点1的代码,但是处理逻辑完全不一样了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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) {
//从三级缓存中获取 此时可以获取到 A 的实例,虽然属性并不太完整
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

    还是从singletonObjects获取对象获取不到,因为A是在singletonsCurrentlyInCreation这个Set中,所以进入了下面的逻辑,从二级缓存earlySingletonObjects中取,还是没有查到,然后从三级缓存singletonFactories找到对应的对象工厂调用getObject方法获取未完全填充完毕的A的实例对象,然后删除三级缓存的数据,填充二级缓存的数据,返回这个对象A。C依赖A的实例填充完毕了,虽然这个A是不完整的。不管怎么样C式填充完了,就可以将C放到一级缓存singletonObjects同时清理二级和三级缓存的数据。同样的流程B依赖的C填充好了,B也就填充好了,同理A依赖的B填充好了,A也就填充好了。Spring就是通过这种方式来解决循环引用的

13.png

Spring不能解决构造器的循环依赖

构造器注入形成的循环依赖: 也就是beanB需要在beanA的构造函数中完成初始化,beanA也需要在beanB的构造函数中完成初始化,这种情况的结果就是两个bean都不能完成初始化,循环依赖难以解决

Spring解决循环依赖主要是依赖三级缓存,但是的在调用构造方法之前还未将其放入三级缓存之中,因此后续的依赖调用构造方法的时候并不能从三级缓存中获取到依赖的Bean,因此不能解决

Spring不能解决prototype作用域循环依赖

这种循环依赖同样无法解决,因为spring不会缓存‘prototype’作用域的bean,而spring中循环依赖的解决正是通过缓存来实现的

Spring不能解决多例的循环依赖

多实例Bean是每次调用一次getBean都会执行一次构造方法并且给属性赋值,根本没有三级缓存,因此不能解决循环依赖

那么实际开发中,类似的依赖是如何解决?

  • 生成代理对象产生的循环依赖

这类循环依赖问题解决方法很多,主要有:

  1. 使用@Lazy注解,延迟加载
  2. 使用@DependsOn注解,指定加载先后关系
  3. 修改文件名称,改变循环依赖类的加载顺序
  • 使用@DependsOn产生的循环依赖

这类循环依赖问题要找到@DependsOn注解循环依赖的地方,迫使它不循环依赖就可以解决问题

  • 多例循环依赖

这类循环依赖问题可以通过把bean改成单例的解决

  • 构造器循环依赖

这类循环依赖问题可以通过使用@Lazy注解解决


Spring 解决循环依赖原理
https://sugayoiya.github.io/posts/11853.html
作者
Sugayoiya
发布于
2021年8月7日
许可协议