一、什么是循环依赖

循环依赖即循环引用,形成闭环。比如,A 依赖 B,B 又依赖 A,形成了循环依赖;或者 A 依赖 B,B 依赖 C,C 又依赖 A,形成了循环依赖;更或者是自己依赖自己。如图:

springcloud按照word模版生成数据有循环数据_构造器

这不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。Spring 循环依赖场景有:

  1. 构造器的循环依赖
  2. 构造器依赖 + field/setter 依赖(A 的构造器依赖 B,B 的 field/setter 依赖 A)
  3. field 依赖或者 setter 依赖

具体情况如下:
1️⃣A 的构造方法中依赖 B 的实例对象,同时 B 的构造方法中依赖 A 的实例对象。(无法解决)
2️⃣A 的构造方法中依赖 B 的实例对象,同时 B 的某个 field/setter 需要 A 的实例对象,以及反之。(可以解决)
3️⃣A 的某个 field/setter 依赖 B 的实例对象,同时 B 的某个 field/setter 依赖 A 的实例对象,以及反之。(可以解决)

二、Spring 三大循环依赖

1️⃣构造器注入循环依赖【不能解决】

表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException表示循环依赖。

@Service
public class A {
    public A(B b) {
    }
}
@Service
public class B {
    public B(A a) {
    }
}

Spring 容器会将每一个正在创建的 Bean 标识符放在一个“当前创建 Bean 池”中,Bean 标识符在创建过程中将一直保持在这个池中,因此如果在创建 Bean 过程中发现自己已经在“当前创建 Bean 池”里时将抛出BeanCurrentlyInCreationException表示循环依赖;而对于创建完毕的 Bean 将从“当前创建 Bean 池”中清除掉。执行结果报错信息为:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:  
    Error creating bean with name 'a': Requested bean is currently in creation:
Is there an unresolvable circular reference?

根本原因:Spring 解决循环依赖依靠的是 Bean 的“中间态”这个概念,而这个中间态指的是已经实例化,但还没初始化的状态。而构造器表示已经完成实例化,所以构造器的循环依赖无法解决。

2️⃣Singleton Pattern field 属性注入循环依赖【可以解决】

@Service
public class A {
    @Autowired
    private B b;
}
 
@Service
public class B {
    @Autowired
    private A a;
}

结果:项目启动成功,能够正常工作。

备注:setter 方法注入方式原理同字段注入方式类似。

3️⃣prototype 模式 field 属性注入循环依赖【不能解决】

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class A {
    @Autowired
    private B b;
}
 
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B {
    @Autowired
    private A a;
}

对于prototype 作用域的 bean,Spring 容器无法完成依赖注入,因为 Spring 容器不缓存 prototype 作用域的 bean,因此无法提前暴露一个创建中的 bean。

scope="prototype"意思是每次请求都会创建一个实例对象。两者的区别是:有状态的 bean 都使用 Prototype 作用域,无状态的一般都使用 singleton 单例作用域。

因此本例中启动时是不会报错的(因为非单例 Bean 默认不会初始化,而是使用时才会初始化),所以需要手动 getBean() 或者在一个单例 Bean 内 @Autowired 一下它即可:

// 在单例Bean内注入
    @Autowired
    private A a;

打印结果:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:  
    Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?

三、怎么检测是否存在循环依赖

检测循环依赖相对比较容易,Bean 在创建的时候可以给该 Bean 打标,如果递归调用回来发现正在创建中的话,说明循环依赖了。

四、Spring 是如果解决循环依赖的

Spring 通过三级缓存加上“提前曝光”机制,配合 Java 的对象引用原理,比较完美地解决了某些情况下的循环依赖问题。

  1. A 首先完成了初始化的第一步,并且将自己提前曝光到 singletonFactories 中。此时进行初始化的第二步,发现自己依赖对象 B,就尝试去 get(B),发现 B 还没有被 create,所以走 create 流程。
  2. B 在初始化第一步的时候发现自己依赖了对象 A,于是尝试 get(A),由于 A 通过 ObjectFactory 将自己提前曝光了,所以 B 能够通过 ObjectFactory.getObject 拿到 A 对象。
  3. B 拿到 A 对象后顺利完成了初始化阶段 1、2、3,完全初始化之后将自己放入到一级缓存 singletonObjects 中。此时返回 A 中,A 拿到 B 的对象顺利完成自己的初始化阶段 2、3,最终 A 也完成了初始化。