SPRING

一文深入分析阿里Java二面神仙问题:Spring循环依赖

什么是循环依赖?举个例子/** * A 类,引入 B 类的属性 b */public class A { private B b;}/** * B 类,引入 A 类的属性 a */publ

什么是循环依赖?

举个例子

/**
 * A 类,引入 B 类的属性 b
 */public class A {
  private B b;}/**
 * B 类,引入 A 类的属性 a
 */public class B {
  private A a;}

再看个简单的图:

68617ffb57da83e8daad3415bc8ffe92.png

像这样,创建 a 的时候需要依赖 b,那就创建 b,结果创建 b 的时候又需要依赖 a,那就创建 a,创建 a 的时候需要依赖 b,那就创建 b,结果创建 b 的时候又需要依赖 a ……

互相依赖何时了,死循环了吧? 诺,这就是循环依赖!


循环依赖其实不算个问题或者错误,我们实际在开发的时候,也可能会用到。 再拿最开始的 A 和 B 来说,我们手动使用的时候会用以下方式:

A a = new A();
  B b = new B();
  b.setA(a);
  a.setB(b);

其实这样就解决了循环依赖,功能上是没有问题的,但是为什么 Spring 要解决循环依赖?

为什么 Spring 要解决循环依赖?

首先简单了解下,我们用 Spring 框架,它帮我们做了什么事情?总结上来说,六字真言:IoC 和 AOP。

由于 Spring 解决循环依赖是考虑到 IoC 和 AOP 相关知识了,所以这里我先提一下。

由于本文主要的核心是 Spring 的循环依赖处理,所以不会对 IoC 和 AOP 做详细的说明,想了解以后有机会再说

IoC,主要是将对象的创建、管理都交给了 Spring 来管理,能够解决对象之间的耦合问题,对开发人员来说也是省时省力的。

AOP,主要是在不改变原有业务逻辑情况下,增强横切逻辑代码,也是解耦合,避免横切逻辑代码重复;也是对 OOP 的延续、补充。

既然类的实例化都交给了 Spring 来管理了,那么循环依赖 Spring 肯定也要考虑到怎么去处理(怎么总觉得有点像是废话 )。

解决循环依赖的方式

参考我们能想到的肯定是手动处理的方式,先将对象都 new 出来,然后进行 set 属性值,而 Spring 也是通过这样的形式来处理的(你说巧不巧?其实一点都不巧 ,后面再说为什么),其实 Spring 管理 Bean 的实例化底层其实是由反射实现的。

而我们实例化的方式也有好多种,比如通过构造函数,一次性将属性赋值,像下面这样

// 假设有学生这个类public class Student {
  private int id;
  private String name;

  public Student(int id, String name) {
    this.id = id;
    this.name = name;
  }}// 通过构造器方式实例化并赋值new Student(1, "Suremotoo");

但是使用构造器这样的方式,是无法解决循环依赖的!为什么不能呢?

我们还是以文中开头的 A 和 B 互相依赖来说, 要通过构造器的方式实现 A 的实例化,如下

new A(b);

Wow,是不是发现问题了?要通过构造器的方式,首先要将属性值实例化出来啊!A 要依赖属性 b,就需要先将 B 实例化,可是 B 的实例化是不是还是需要依赖 A❗️ 这不就是文中开头描述的样子嘛,所以通过构造器的方式,Spring 也没有办法解决循环依赖。

我们使用 set 可以解决,那么 Spring 也使用 set 方式呢?答案是可以的。

既然底层是通过反射实现的,我们自己也用反射实现的话,大概思路是这样的(还是以 A 和 B 为例)

  1. 先实例化 A 类

  2. 再实例化 B 类

  3. set B 类中的 a 属性

  4. set A 类中的 b 属性

其实就是通过反射,实现以下代码

      A a = new A();
      B b = new B();
      b.setA(a);
      a.setB(b);

这里可以稍微说明一下,为什么这样可以?

A a = new A(),说明 A 只是实例化,还未初始化

同理,B b = new B() 也只是实例化,并未初始化

a.setB(b);, 对 a 的属性赋值,完成 a 的初始化

b.setA(a);, 对 b 的属性赋值,完成 b 的初始化

现在是不是有点感觉了,先把狗骗进来,再杀

Spring 如何解决循环依赖问题

先上个通俗的答案解释,三级缓存。

  /**
   * 单例对象的缓存:bean 名称——bean 实例,即:所谓的单例池。
   * 表示已经经历了完整生命周期的 Bean 对象
   * <b>第一级缓存</b

原创不易,完成人机校验,阅读全文

相关推荐