动力节点旗下在线教育品牌  |  咨询热线:400-8080-105 学Java全栈,上蛙课网
首页 > 文章

Spring之循环依赖详解

07-22 16:42 327浏览
举报 T字号
  • 大字
  • 中字
  • 小字

Spring循环依赖问题一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为这也是许多互联网公司面试时的高频面试题。

我们在探究这个问题之前,必须要搞清楚什么是Spring中的循环依赖。我们只是从字面上来理解的话就是A依赖B的同时B也依赖了A,完成了一个依赖的循环过程。

体现到代码层次就是这个样子:

@Component

public class A {

    // A中注入了B

@Autowired

private B b;

}

@Component

public class B {

    // B中也注入了A

@Autowired

private A a;

}

当然,这是最常见的一种循环依赖,比较特殊的还有

// 自己依赖自己

@Component

public class A {

    // A中注入了A

@Autowired

private A a;

}

虽然体现形式不一样,但是实际上都是循环依赖的问题。

Spring解决循环依赖是有前置条件的:

1.出现循环依赖的Bean必须要是单例

2.依赖注入的方式不能全是构造器注入的方式

接下来是Spring bean的创建,其本质上还是一个对象的创建,一个完整的对象包含两部分:当前对象实例化和对象属性的实例化。Spring bean也不例外。

Spring中,对象的实例化是通过反射实现的,而对象的属性则是在对象实例化之后通过一定的方式设置的。这是对于循环依赖的理解的催化剂,我们这里以两个类A和B为例进行讲解,如下是A和B的声明:

@Component

public class A {

  private B b;

  public void setB(B b) {

    this.b = b;

  }

}

@Component

public class B {

  private A a;

  public void setA(A a) {

    this.a = a;

  }

}

从上面的代码可以看出,这里A和B中各自都以对方为自己的全局属性。这里首先需要说明的一点,Spring实例化bean是通过ApplicationContext.getBean()方法来进行的。如果要获取的对象依赖了另一个对象,那么其首先会创建当前对象,然后通过递归的调用ApplicationContext.getBean()方法来获取所依赖的对象,最后将获取到的对象注入到当前对象中。

这里我们以上面的首先初始化A对象实例为例进行讲解。首先Spring尝试通过ApplicationContext.getBean()方法获取A对象的实例,由于Spring容器中还没有A对象实例,因而其会创建一个A对象然后发现其依赖了B对象,因而会尝试递归的通过ApplicationContext.getBean()方法获取B对象的实例

但是Spring容器中此时也没有B对象的实例,因而其还是会先创建一个B对象的实例。

我们需要注意这个时间点,此时A对象和B对象都已经创建了,并且保存在Spring容器中了,只不过A对象的属性b和B对象的属性a都还没有设置进去在前面Spring创建B对象之后,Spring发现B对象依赖了属性A,因而还是会尝试递归的调用ApplicationContext.getBean()方法获取A对象的实例因为Spring中已经有一个A对象的实例,虽然只是半成品(其属性b还未初始化),但其也还是目标bean,因而会将该A对象的实例返回。

此时,B对象的属性a就设置进去了,然后还是ApplicationContext.getBean()方法递归的返回,也就是将B对象的实例返回,此时就会将该实例设置到A对象的属性b中。与此同时,注意A对象的属性b和B对象的属性a都已经设置了目标对象的实例了

这里的A对象其实和前面设置到实例B中的半成品A对象是同一个对象,其引用地址是同一个,这里为A对象的b属性设置了值,其实也就是为那个半成品的a属性设置了值。

我们需要注意的一个点,Spring是如何标记开始生成的A对象是一个半成品,并且是如何保存A对象的。这里的标记工作Spring是使用ApplicationContext的属性SetsingletonsCurrentlyInCreation来保存的,而半成品的A对象则是通过MapsingletonFactories来保存的这里的ObjectFactory是一个工厂对象,可通过调用其getObject()方法来获取目标对象。在AbstractBeanFactory.doGetBean()方法中获取对象的方法如下:

protected  T doGetBean(final String name, @Nullable final Class requiredType,

    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

  

  // 尝试通过bean名称获取目标bean对象,比如这里的A对象

  Object sharedInstance = getSingleton(beanName);

  // 我们这里的目标对象都是单例的

  if (mbd.isSingleton()) {

    // 这里就尝试创建目标对象,第二个参数传的就是一个ObjectFactory类型的对象,这里是使用Java8的lamada

    // 表达式书写的,只要上面的getSingleton()方法返回值为空,则会调用这里的getSingleton()方法来创建

    // 目标对象

    sharedInstance = getSingleton(beanName, () -> {

      try {

        // 尝试创建目标对象

        return createBean(beanName, mbd, args);

      } catch (BeansException ex) {

        throw ex;

      }

    });

  }

  return (T) bean;

}

这里的doGetBean()方法是非常关键的一个方法第一个步骤的getSingleton()方法的作用是尝试从缓存中获取目标对象,如果没有获取到,则尝试获取半成品的目标对象;如果第一个步骤没有获取到目标对象的实例,那么就进入第二个步骤第二个步骤的getSingleton()方法的作用是尝试创建目标对象,并且为该对象注入其所依赖的属性。这里其实就是主干逻辑,我们前面图中已经标明,在整个过程中会调用三次doGetBean()方法第一次调用的时候会尝试获取A对象实例,此时走的是第一个getSingleton()方法,由于没有已经创建的A对象的成品或半成品,因而这里得到的是null然后就会调用第二个getSingleton()方法,创建A对象的实例,然后递归的调用doGetBean()方法,尝试获取B对象的实例以注入到A对象中此时由于Spring容器中也没有B对象的成品或半成品,因而还是会走到第二个getSingleton()方法,在该方法中创建B对象的实例

创建完成后,尝试获取其所依赖的A的实例作为其属性,因而还是会递归的调用doGetBean()方法。由于在前面已经有了一个半成品的A对象的实例,再尝试获取A对象的实例的时候,会走第一个getSingleton()方法在该方法中会得到一个半成品的A对象的实例,然后将该实例返回,并且将其注入到B对象的属性a中,此时B对象实例化完成然后,将实例化完成的B对象递归的返回,此时就会将该实例注入到A对象中,这样就得到了一个成品的A对象。

我们这里可以阅读上面的第一个getSingleton()方法:

@Nullable

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

  

  // 尝试从缓存中获取成品的目标对象,如果存在,则直接返回

  Object singletonObject = this.singletonObjects.get(beanName);

  

  // 如果缓存中不存在目标对象,则判断当前对象是否已经处于创建过程中,在前面的讲解中,第一次尝试获取A对象

  // 的实例之后,就会将A对象标记为正在创建中,因而最后再尝试获取A对象的时候,这里的if判断就会为true

  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

    

    synchronized (this.singletonObjects) {

      singletonObject = this.earlySingletonObjects.get(beanName);

      if (singletonObject == null && allowEarlyReference) {

        

        // 这里的singletonFactories是一个Map,其key是bean的名称,而值是一个ObjectFactory类型的

        // 对象,这里对于A和B而言,调用图其getObject()方法返回的就是A和B对象的实例,无论是否是半成品

        ObjectFactory singletonFactory = this.singletonFactories.get(beanName);

        if (singletonFactory != null) {

          

          // 获取目标对象的实例

          singletonObject = singletonFactory.getObject();

          this.earlySingletonObjects.put(beanName, singletonObject);

          this.singletonFactories.remove(beanName);

        }

      }

    }

  }

  return singletonObject;

}

这里我们会存在一个问题就是A的半成品实例是如何实例化的,然后是如何将其封装为一个ObjectFactory类型的对象,并且将其放到上面的singletonFactories属性中的。

这主要是在前面的第二个getSingleton()方法中,其最终会通过其传入的第二个参数,从而调用createBean()方法,该方法的最终调用是委托给了另一个doCreateBean()方法进行的

这里面有如下一段代码

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)

  throws BeanCreationException {

  // 实例化当前尝试获取的bean对象,比如A对象和B对象都是在这里实例化的

  BeanWrapper instanceWrapper = null;

  if (mbd.isSingleton()) {

    instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);

  }

  if (instanceWrapper == null) {

    instanceWrapper = createBeanInstance(beanName, mbd, args);

  }

  // 判断Spring是否配置了支持提前暴露目标bean,也就是是否支持提前暴露半成品的bean

  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences

    && isSingletonCurrentlyInCreation(beanName));

  if (earlySingletonExposure) {

    // 如果支持,这里就会将当前生成的半成品的bean放到singletonFactories中,这个singletonFactories

    // 就是前面第一个getSingleton()方法中所使用到的singletonFactories属性,也就是说,这里就是

    // 封装半成品的bean的地方。而这里的getEarlyBeanReference()本质上是直接将放入的第三个参数,也就是

    // 目标bean直接返回

    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

  }

  try {

    // 在初始化实例之后,这里就是判断当前bean是否依赖了其他的bean,如果依赖了,

    // 就会递归的调用getBean()方法尝试获取目标bean

    populateBean(beanName, mbd, instanceWrapper);

  } catch (Throwable ex) {

    // 省略...

  }

  return exposedObject;

}

到这里,Spring整个解决循环依赖问题的实现思路已经豁然开朗。对于整过程,我们只要理解两点

1.Spring是通过递归的方式获取目标bean及其所依赖的bean的;

2.Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性。

结合这两点,可以知道Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性的。

在看完了本文对Spring中循环依赖的解答,其实整个过程还是蛮复杂的,需要有一定的理解能力才能完全弄清楚其中的各种原理和处理方式。还没有完全理解的同学也不要着急,可以在本站观看spring视频课程来加深自己的理解,让自己的学习更进一步。

0人推荐
共同学习,写下你的评论
0条评论
代码小兵1271
程序员代码小兵1271

11篇文章贡献55922字

作者相关文章更多>

推荐相关文章更多>

Java数据结构

HelloWorld10-31 08:24

浅谈MySQL中SQL优化的常用方法

军哥08-12 23:29

五分钟读懂UML类图

江湖人称小李白12-10 10:41

MyBatis开发框架的四大核心

IT逐梦者08-17 21:43

一次搞定continue,break和return

HelloWorld11-06 11:19

发评论

举报

0/150

取消