参考文献

循环依赖

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

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

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

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

    img

Spring如何检测循环依赖

  • 创建单例Bean时,会调用下面的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean ==>
    // org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>) ==>
    // org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation

    /**
    * Callback before singleton creation.
    * <p>The default implementation register the singleton as currently in creation.
    * @param beanName the name of the singleton about to be created
    * @see #isSingletonCurrentlyInCreation
    */
    protected void beforeSingletonCreation(String beanName) {
    // inCreationCheckExclusions 集合通常用于存储那些在创建检查(循环依赖检查)时需要排除的 bean 名称。如果当前 bean 名称在 inCreationCheckExclusions 中,则不满足条件
    // singletonsCurrentlyInCreation 集合用于记录当前正在创建的单例 bean 的名称。如果添加当前 bean 的名称失败(即已经存在,说明当前 bean 正在创建的过程中),则不满足条件
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
    }
    }
  • Prototype情况

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // TODO 4. 原型模式的依赖检查
    // 只有在单例情况才会尝试解决循环依赖
    // 在原型模式下,如果存在A中有B的属性,B中有A的属性,
    // 那么当依赖注入的时候,就会产生还未创建完的时候因为对B创建再次返回创建A,造成循环依赖,即下述情况
    // Fail if we're already creating this bean instance:
    // We're assumably within a circular reference.
    if (isPrototypeCurrentlyInCreation(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    if (mbd.isPrototype()) {
    // 以prototype模式来创建bean
    // It's a prototype -> create a new instance.
    Object prototypeInstance = null;
    try {
    // 将beanName放入正在创建的列表中
    beforePrototypeCreation(beanName);
    prototypeInstance = createBean(beanName, mbd, args);
    }
    finally {
    // 将beanName从正在创建的列表中移除
    afterPrototypeCreation(beanName);
    }
    beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
    }

循环依赖的N种场景

img

单例的Setter注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.holelin.sundry.test;

import org.springframework.stereotype.Service;

@Service
public class TestService1 {

private TestService2 testService2;

public void setTestService2(TestService2 testService2) {
this.testService2 = testService2;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.holelin.sundry.test;

import org.springframework.stereotype.Service;

@Service
public class TestService2 {

private TestService1 testService1;

public void setTestService1(TestService1 testService1) {
this.testService1 = testService1;
}
}
  • 这是一个经典的循环依赖,但是它能正常运行,得益于Spring的内部机制,让我们根本无法感知它有问题,因为Spring默默帮我们解决了.

  • spring内部有三级缓存:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 一级缓存
    /** Cache of singleton objects: bean name to bean instance. */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    // 三级缓存
    /** Cache of singleton factories: bean name to ObjectFactory. */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    // 二级缓存
    /** Cache of early singleton objects: bean name to bean instance. */
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    • singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例
      • 这一层缓存是最终的单例对象,也就是在实例化后并且依赖注入完成的对象.在这层缓存中,每个bean定义只保存一个单例对象实例,在整个容器生命周期内提供访问单例bean的唯一途径
    • earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例
      • 这一层缓存是用来存放实例化后却尚未初始化(即实例化后还没有进行属性或自身方法的注入或调用)的单例对象.具体原因是当出现A、B两个单例bean相互依赖,并且这两个类都没有使用构造函数注入的方式时,Spring只好实例化其中一个bean并保存在earlySingletonObjects中等待后续的BeanPostProcessor进行另一个bean的注入工作.
      • 保存在该缓存中的Bean所实现Aware子接口的方法还未回调,自定义初始化方法未执行,也未经过BeanPostProcessor实现类的postProcessorBeforeInitialization、postProcessorAfterInitialization方法处理。如果启用了Spring AOP,并且处于切点表达式处理范围之内,那么会被增强,即创建其代理对象。
      • 普通Bean被增强(JDK动态代理或CGLIB)的时机是在AbstractAutoProxyCreator实现的BeanPostProcessorpostProcessorAfterInitialization方法中,而处于循环引用状态的Bean被增强的时机是在AbstractAutoProxyCreator实现的SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference方法中。
    • singletonFactories 三级缓存,用于保存bean创建工厂,以便于后面扩展有机会创建代理对象.
      • 这一层缓存是用来存放正在创建单例bean实例的工厂对象.Bean被实例化时,会调用它们的getObject()获得目标Bean实例,并缓存这个实例.在缓存这个实例前,还会将提供该实例的工厂对象缓存起来用于解决循环依赖问题

img

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
   protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

final String beanName = transformedBeanName(name);
Object bean;

// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
// ... 省略
}
/**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 从singletonObjects一级缓存中获取实例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 从earlySingletonObjects二级缓存中获取实例
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 从singletonFactories三级缓存中获取实例
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 加入到二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
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
56
57
58
59
60
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// ...省略
// Create bean instance.
if (mbd.isSingleton()) {
// 分两步:
// 1. createBean-->doCreateBean-->addSingletonFactory加入到三级缓存中
// 2. getSingleton-->addSingleton加入到一级缓存中
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// ...省略
}

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// ...省略
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 提前暴露
if (earlySingletonExposure) {
// ...省略
// 加入到三级缓存中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// ...省略
}
/**
* Add the given singleton factory for building the specified singleton
* if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
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);
}
}
}
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
/**
* Return the (raw) singleton object registered under the given name,
* creating and registering a new one if none registered yet.
* @param beanName the name of the bean
* @param singletonFactory the ObjectFactory to lazily create the singleton
* with, if necessary
* @return the registered singleton object
*/
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// ...省略
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
// ...省略
if (newSingleton) {
// 实例创建完成后加入到一级缓存中
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}

多例的Setter注入

  • 这种注入方法偶然会有,特别是在多线程的场景下,具体代码如下:
1
2
3
4
5
6
7
8
9
10
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class TestService1 {

@Autowired
private TestService2 testService2;

public void test1() {
}
}
1
2
3
4
5
6
7
8
9
10
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class TestService2 {

@Autowired
private TestService1 testService1;

public void test2() {
}
}
  • 很多人说这种情况Spring容器启动会报错,其实是不对的,但是程序能够正常启动.
  • 其实在AbstractApplicationContext类的refresh方法中告诉了我们答案,它会调用finishBeanFactoryInitialization方法,该方法的作用是为了Spring容器启动的时候提前初始化一些bean.该方法的内部又调用了preInstantiateSingletons方法
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
// 方法路径: AbstractApplicationContext#refresh-->finishBeanFactoryInitialization-->preInstantiateSingletons
// Trigger initialization of all non-lazy singleton beans...
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// 非抽象、单例 并且非懒加载的类才能被提前初始bean
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof FactoryBean) {
final FactoryBean<?> factory = (FactoryBean<?>) bean;
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
((SmartFactoryBean<?>) factory)::isEagerInit,
getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
getBean(beanName);
}
}
}
  • 而多例即SCOPE_PROTOTYPE类型的类,非单例,不会被提前初始化bean,所以程序能够正常启动

  • 如何让他提前初始化bean呢?只需要再定义一个单例的类,在它里面注入TestService1

    1
    2
    3
    4
    5
    6
    @Service
    public class TestService3 {

    @Autowired
    private TestService1 testService1;
    }
  • 运行启动后报错:

    1
    Requested bean is currently in creation: Is there an unresolvable circular reference?

注意:这种循环依赖问题是无法解决的,因为它没有用缓存,每次都会生成一个新对象.

构造器注入

1
2
3
4
5
6
@Service
public class TestService1 {

public TestService1(TestService2 testService2) {
}
}
1
2
3
4
5
6
@Service
public class TestService2 {

public TestService2(TestService1 testService1) {
}
}
  • 启动报错Requested bean is currently in creation: Is there an unresolvable circular reference?

img

单例的代理对象Setter注入

  • 这种注入方式其实也比较常用,比如平时使用:@Async注解的场景,会通过AOP自动生成代理对象
1
2
3
4
5
6
7
8
9
10
@Service
public class TestService1 {

@Autowired
private TestService2 testService2;

@Async
public void test1() {
}
}
1
2
3
4
5
6
7
8
9
@Service
public class TestService2 {

@Autowired
private TestService1 testService1;

public void test2() {
}
}
1
org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'testService1': Bean with name 'testService1' has been injected into other beans [testService2] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

img

  • 如果这时候把TestService1改个名字,改成:TestService6,其他的都不变.再重新启动一下程序,神奇般的好了.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Service
    public class TestService6 {

    @Autowired
    private TestService2 testService2;

    @Async
    public void test1() {
    }
    }
  • 这就要从spring的bean加载顺序说起了,默认情况下,Spring是按照文件完整路径递归查找的,按路径+文件名排序,排在前面的先加载.所以TestService1比TestService2先加载,而改了文件名称之后,TestService2比TestService6先加载.

img

img

DependsOn循环依赖

  • 还有一种有些特殊的场景,比如我们需要在实例化Bean A之前,先实例化Bean B,这个时候就可以使用@DependsOn注解
1
2
3
4
5
6
7
8
9
10
@DependsOn(value = "testService2")
@Service
public class TestService1 {

@Autowired
private TestService2 testService2;

public void test1() {
}
}
1
2
3
4
5
6
7
8
9
10
@DependsOn(value = "testService1")
@Service
public class TestService2 {

@Autowired
private TestService1 testService1;

public void test2() {
}
}
  • 程序启动之后,执行结果:
1
Circular depends-on relationship between 'testService2' and 'testService1'
  • 这个个例子中本来如果TestService1和TestService2都没有加@DependsOn注解是没问题的,反而加了这个注解会出现循环依赖问题.
  • 它会检查dependsOn的实例有没有循环依赖,如果有循环依赖则抛异常.

img

循环处理的解决方案

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

  • 这类循环依赖问题解决方法很多,主要有:
    1. 使用@Lazy注解,延迟加载
    2. 使用@DependsOn注解,指定加载先后关系
    3. 修改文件名称,改变循环依赖类的加载顺序

使用@DependsOn产生的循环依赖

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

多例循环依赖

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

构造器循环依赖

  • 这类循环依赖问题可以通过使用@Lazy注解解决.
  • 通过将其中一个实例以方法参数注入,而不是通过构造器依赖注入来解决该问题.这被称为setter方法依赖注入.这种方法允许先创建一个Bean,然后使用setter方法来注入其他Bean,而不是通过构造器进行依赖注入,从而解决了构造器循环依赖.