Spring中的循环引用
1. 什么是 Spring 的循环依赖?
简单的来说就是 A 依赖 B 的同时,B 依赖 A。在创建 A 对象的同时需要使用 B 对象,在创建 B 对象的同时需要使用到 A 对象。如下代码所示:
@Component
public class A {
public A(){
System.out.println("A的构造方法执行了...");
}
private B b;
@Autowired
public void setB(B b) {
this.b = b;
System.out.println("给A注入B");
}
}
@Component
public class B {
public B(){
System.out.println("B的构造方法执行了...");
}
private A a;
@Autowired
public void setA(A a) {
this.a = a;
System.out.println("给B注入了A");
}
}
2. 出现循环依赖以后会有什么问题?
对象的创建过程会产生死循环,如下所示:

3. Spring 如何解决循环依赖的?
Spring 的 Bean 生命周期创建 bean 的过程回顾:

Spring 解决循环依赖是通过三级缓存,对应的三级缓存如下所示:

| 缓存 | 源码名称 | 作用 |
|---|---|---|
| 一级缓存 | singletonObjects |
单例池;缓存已经经历了完整声明周期,已经初始化完成的 bean 对象。 |
| 二级缓存 | earlySingletonObjects |
缓存早期的 bean 对象。(生命周期还没有走完) |
| 三级缓存 | singletonFactories |
缓存的是 ObjectFactory 表示对象工厂,用来创建某个对象的。 |
一级缓存的作用:

一级缓存解决不了循环依赖问题。

二级缓存的作用:
如果要想打破上述的循环,就需要一个中间人的参与,这个中间人就是缓存。

步骤如下所示:
-
实例化 A 得到 A 的原始对象。
-
将 A 的原始对象存储到二级缓存(
earlySingletonObjects)中。 -
需要注入 B,B 对象在一级缓存中不存在,此时实例化 B,得到原始对象 B。
-
将 B 的原始对象存储到二级缓存中。
-
需要注入 A,从二级缓存中获取 A 的原始对象。
-
B 对象创建成功。
-
将 B 对象加入到一级缓存中。
-
将 B 注入给 A,A 创建成功。
-
将 A 对象添加到一级缓存中。
三级缓存的作用:
从上面这个分析过程中可以得出,只需要一个缓存就能解决循环依赖了,那么为什么 Spring 中还需要 singletonFactories?
基于上面的场景想一个问题:如果 A 的原始对象注入给 B 的属性之后,A 的原始对象进行了 AOP 产生一个代理对象,此时就会出现,对于 A 而言,它的 Bean 对象其实应该是 AOP 之后的代理对象,而 B 的 a 属性对应的并不是 AOP 之后的代理对象,这就产生了冲突。也就是说,最终单例池中存放的 A 对象(代理对象)和 B 依赖的 A 对象不是同一个。
所以在该场景下,上述提到的二级缓存就解决不了了。那这个时候 Spring 就利用了第三级缓存 singletonFactories 来解决这个问题。
singletonFactories 中存的是某个 beanName 对应的 ObjectFactory,在 bean 的生命周期中,生成完原始对象之后,就会构造一个 ObjectFactory 存入 singletonFactories 中,后期其他的 Bean 可以通过调用该 ObjectFactory 对象的 getObject 方法获取对应的 Bean。
整体的解决循环依赖问题的思路如下所示:

步骤如下所示:
- 实例化 A,得到原始对象 A,并且同时生成一个原始对象 A 对应的
ObjectFactory对象。 - 将
ObjectFactory对象存储到三级缓存中。 - 需要注入 B,发现 B 对象在一级缓存和二级缓存都不存在,并且三级缓存中也不存在 B 对象所对应的
ObjectFactory对象。 - 实例化 B,得到原始对象 B,并且同时生成一个原始对象 B 对应的
ObjectFactory对象,然后将该ObjectFactory对象也存储到三级缓存中。 - 需要注入 A,发现 A 对象在一级缓存和二级缓存都不存在,但是三级缓存中存在 A 对象所对应的
ObjectFactory对象。 - 通过 A 对象所对应的
ObjectFactory对象创建 A 对象的代理对象。 - 将 A 对象的代理对象存储到二级缓存中。
- 将 A 对象的代理对象注入给 B,B 对象执行后面的生命周期阶段,最终 B 对象创建成功。
- 将 B 对象存储到一级缓存中。
- 将 B 对象注入给 A,A 对象执行后面的生命周期阶段,最终 A 对象创建成功,将二级缓存的 A 的代理对象存储到一级缓存中。
注意:
- 后面的生命周期阶段会按照本身的逻辑进行 AOP,在进行 AOP 之前会判断是否已经进行了 AOP,如果已经进行了 AOP 就不会进行 AOP 操作了。
singletonFactories: 缓存的是一个ObjectFactory,主要用来去生成原始对象进行了 AOP 之后得到的代理对象, 在每个 Bean 的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本 bean,那么这个工厂无用,本 bean 按照自己的生命周期执行,执行完后直接把本 bean 放入singletonObjects中即可,如果出现了循环依赖了本 bean,则另外那个 bean 执行ObjectFactory提交得到一个 AOP 之后的代理对象(如果没有 AOP,则直接得到一个原始对象)。
4. 只有一级缓存和三级缓存是否可行?
不行,每次从三级缓存中拿到 ObjectFactory 对象,执行 getObject() 方法又会产生新的代理对象,因为 A 是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了 objectFactory.getObject() 产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍 objectFactory.getObject() 方法再产生一个新的代理对象,保证始终只有一个代理对象。
总结:所以如果没有 AOP 的话确实可以两级缓存就可以解决循环依赖的问题,如果加上 AOP,两级缓存时无法解决的,不可能每次执行 objectFactory.getObject() 方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保护产生的代理对象。
5. 构造方法出现了循环依赖怎么解决?
Spring 中大部分的循环依赖已经帮我们解决掉了,但是有一些循环依赖还需要我们程序员自己进行解决;如果构造方法出现了循环依赖可以在构造参数前面加 @Lazy 注解,就不会真正的注入真实对象,该注入对象会被延迟加载。此时注入的是一个代理对象。

浙公网安备 33010602011771号