首页 > 图灵资讯 > 技术篇>正文

Spring之循环依赖详解

2023-04-03 15:04:47

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

  在探索这个问题之前,我们必须弄清楚什么是循环依赖于Spring。我们只是从字面上理解,那就是同时,A依赖B,B也依赖A,完成了一个依赖循环。

  这就是代码层次的体现:

  @Component

  public class A {

  // B注入A

  @Autowired

  private B b;

  }

  @Component

  public class B {

  // A也注入到B中

  @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是通过applicationcontextex.getBean()进行方法。如果要获得的对象依赖于另一个对象,它将首先创建当前对象,然后通过递归调用applicationcontextextextextex.getBean()获取所依赖对象的方法,最后将获得对象注入当前对象。

  在这里,我们将首先初始化上述内容以A对象实例为例进行讲解。首先,Spring试图通过Applicationcontextttextion。.getBean()获取A对象实例的方法,因为Spring容器中没有A对象实例,所以它会创建A对象。然后发现了它的依赖因此,B对象会尝试通过Aplicationcontextextet递归.getBean()获取B对象实例的方法。

  但是此时Spring容器中没有B对象的例子,因此它将首先创建B对象的例子。

  我们这个时间点要注意,这个时候A对象和B对象已经创建并保存在Spring容器中,但A对象的属性B和B对象的属性A尚未设置。在前面Spring创建B对象后,Spring发现B对象依赖属性A,因此仍将尝试将ApplicationContextext转回.getBean()获取A对象实例的方法。因为Spring中已经有一个A对象的例子。虽然只是半成品(其属性b尚未初始化),但仍然是目标bean,所以会返回A对象的例子。

  此时,将B对象的属性a设置为Aplicationcontexttextte.getBean()方法递归返回,即B对象的实例返回,此时将该实例设置在A对象的属性B中。与此同时,注意A对象的属性B和B对象的属性A都设置了目标对象的实例。

  这里的A对象实际上与之前设置在实例B中的半成品A对象相同,引用地址相同。这里为A对象的B属性设置了值,实际上是为半成品的A属性设置了值。

  我们需要注意的一点,Spring是如何标记开始生成的A对象是一个半成品,以及如何保存A对象的?这里的标记工作Spring是使用Aplicationcontext的属性Setsingletonscureng。tlyincreation用于保存,而半成品的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类型的对象,它使用Java8lamadada

  // 表达式写作,只要上述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/span>

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

  synchronized (this.singletonObjects) {

  singletonObject = this.earlySingletonObjects.get(beanName);

  if (singletonObject == null && allowEarlyReference) {

  // 这里的singletonFactories是Map,其key是bean的名字,值是objectFactory类型

  // 对象,对于A和B来说,A和B对象的例子是通过调用图其getobject()来返回的,不管是不是半成品。

  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属性中。

  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

上一篇 实现线程安全的方法
下一篇 Java情报站:Java 15的新功能

文章素材均来源于网络,如有侵权,请联系管理员删除。