8

再读Spring源码之一 Spring如何对付循环引用

 3 years ago
source link: https://blog.duval.top/2020/08/21/%E5%86%8D%E8%AF%BBSpring%E6%BA%90%E7%A0%81%E4%B9%8B%E4%B8%80-Spring%E5%A6%82%E4%BD%95%E5%AF%B9%E4%BB%98%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

关于Spring如何解决循环依赖这个问题,以前写过一篇文章Spring源码分析3-对Bean循环依赖的处理。但是如今回头看,写得还是略粗糙,而且细节也忘得差不多了。所以决定再写一写,重新理一遍思路。

Bean循环依赖

spring框架的bean可以互相依赖,因此,可能存在循环依赖的情况,例如:

    <!--构造器循环依赖(单例)-->
    <bean id = "testA" class="org.demo.circle.TestA">
        <constructor-arg index = "0" ref="testB"/>
    </bean>

    <bean id = "testB" class="org.demo.circle.TestB">
        <constructor-arg index = "0" ref="testC"/>
    </bean>

    <bean id = "testC" class="org.demo.circle.TestC">
        <constructor-arg index = "0" ref="testA"/>
    </bean>

    <!--构造器循环依赖(prototype)-->
    <bean id = "testA0" class="org.demo.circle.TestA" scope="prototype">
        <constructor-arg index = "0" ref="testB0"/>
    </bean>

    <bean id = "testB0" class="org.demo.circle.TestB" scope="prototype">
        <constructor-arg index = "0" ref="testC0"/>
    </bean>

    <bean id = "testC0" class="org.demo.circle.TestC" scope="prototype">
        <constructor-arg index = "0" ref="testA0"/>
    </bean>

    <!--属性注入循环依赖(单例)-->
    <bean id = "testA1" class="org.demo.circle.TestA">
        <property name="testB" ref = "testB1"></property>
    </bean>

    <bean id = "testB1" class="org.demo.circle.TestB">
        <property name="testC" ref = "testC1"></property>
    </bean>

    <bean id = "testC1" class="org.demo.circle.TestC">
        <property name="testA" ref = "testA1"></property>
    </bean>

    <!--属性注入循环依赖(prototype)-->
    <bean id = "testA2" class="org.demo.circle.TestA" scope="prototype">
        <property name="testB" ref = "testB2"></property>
    </bean>

    <bean id = "testB2" class="org.demo.circle.TestB" scope="prototype">
        <property name="testC" ref = "testC2"></property>
    </bean>

    <bean id = "testC2" class="org.demo.circle.TestC" scope="prototype">
        <property name="testA" ref = "testA2"></property>
    </bean>

如上配置文件,包含常见的几种循环依赖,可以归类为以下两大类:

  • 通过构造器注入形成循环依赖:

    • testA –> testB –> testC –> testA –> …
    • testA0 –> testB0 –> testC0 –> testA0 –> …
  • 通过属性注入形成循环依赖:

    • testA1 –> testB1 –> testC1 –> testA1 –> …
    • testA2 –> testB2 –> testC2 –> testA2 –> …

按照bean的类型又可以细分为单例和prototype两种(其他scope类型不常用暂不讨论)

本文主要探讨spring对这几种循环依赖的处理策略和原理。

Spring处理策略

先来说结论,通过测试用例我们可以简单验证以上几种循环依赖的处理策略:


  /**
     * 测试构造器注入循环依赖(单例)
     */
    @Test(expected = BeanCreationException.class)
    public void testCircleRefByConstructor() {
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("circle.xml"));
        TestA testA = (TestA) beanFactory.getBean("testA");
    }

    /**
     * 测试构造器注入循环依赖(prototype类型)
     */
    @Test(expected = BeanCreationException.class)
    public void testCircleRefByConstructorForPrototype() {
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("circle.xml"));
        TestA testA = (TestA) beanFactory.getBean("testA0");
    }

    /**
     * 测试属性注入循环依赖(单例)
     */
    @Test
    public void testCircleRefBySetter() {
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("circle.xml"));
        TestA testA = (TestA) beanFactory.getBean("testA1");
    }


    /**
     * 测试的属性注入循环依赖(prototype类型)
     */
    @Test(expected = BeanCreationException.class)
    public void testPrototypeRefBySetter() {
        BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("circle.xml"));
        TestA testA = (TestA) beanFactory.getBean("testA2");
    }

本文的测试用例代码可以移步spring-learn仓库

测试结果表明:只有单例模式下通过属性注入才能够实现循环依赖,其他情况下都将抛出以下异常BeanCreationException。

调用时序图

我们从上边的测试用例出发分析源码实现,主要涉及的类包括XmlBeanFactory及其父类AbstractBeanFactory,和DefaultSingletonBeanRegistry以及它的父类AbstractAutowireCapableBeanFactory。核心的方法如下时序图:

spring-bean-加载时序图.png

spring-bean-加载时序图.png

读者可以沿着这个时序图的思路去阅读下bean初始化构建的源码。

检查缓存的单例

我们直接从AbstractBeanFactory#doGetBean开始深入。这个方法一开始先调用getSingleton(beanName)检查缓存里是否有名为name的单例Bean缓存。

protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
            throws BeansException {

  String beanName = transformedBeanName(name);
  Object bean;

  // 获取已经注册的单例bean实例
  Object sharedInstance = getSingleton(beanName);

  // ...

}

注意getSingleton(beanName)默认传入的allowEarlyReference参数为true。EarlyReference(早期引用)其实就是spring将还没有初始化完毕的单例bean放进缓存的bean引用,当发生循环依赖的时候,可以直接引用这个早期引用,而不会发生死循环不断地进行bean初始化。

@Override
@Nullable
public Object getSingleton(String beanName) {
  return getSingleton(beanName, true);
}

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 从singletonObjects获取已经初始化完毕的单例对象
  Object singletonObject = this.singletonObjects.get(beanName);
  // 判断该bean是不是正在初始化的单例bean
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    synchronized (this.singletonObjects) {
      // 从earlySingletonObjects中获取单例bean
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // 从singletonFactories缓存里获取单例工厂对象
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
          // 通过单例工厂对象中获取早期单例bean对象,并缓存到earlySingletonObjects中
          singletonObject = singletonFactory.getObject();
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  return singletonObject;
}

public boolean isSingletonCurrentlyInCreation(String beanName) {
  return this.singletonsCurrentlyInCreation.contains(beanName);
}

总结下,getSingleton的流程就是:

  • 如果该单例已经初始化完毕,直接返回;
  • 如果是在建的单例,则尝试返回单例bean早期引用(也就是尚未构建完的半成品bean引用);
  • 如果有单例工厂对象,则使用该对象获取单例bean早期引用,加进缓存并返回;

这里先记住这个几个缓存很重要,尤其关注singletonFactories,下文会用到它。

检查prototype循环依赖

上述检查单例如果返回null,则会进入Prototype的循环依赖检测:

Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
  // ...
} else {
  // prototype循环依赖检测
  if (isPrototypeCurrentlyInCreation(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
  }
  // ...
}         

这里检查到该bean是prototype,而且处于构建中状态,则意味着形成了循环依赖,直接抛异常终止初始化过程。

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
  Object curVal = this.prototypesCurrentlyInCreation.get();
  return (curVal != null &&
      (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}

检查方法很简单,通过名字相同来判断是否为同一个bean。注意因为是prototype,所以缓存字段prototypesCurrentlyInCreation可能为Set,包含多个bean实例。

因此我们知道,prototype的bean无论是构造器或者属性注入,都不允许实现循环依赖

构建不同类型的实例

根据bean类型,分别有各自的构建类型,分为单例、prototype和其他类型三种情况:

//单例
if (mbd.isSingleton()) {
  sharedInstance = getSingleton(beanName, () -> {
    try {
      return createBean(beanName, mbd, args);
    }
    catch (BeansException ex) {
      // Explicitly remove instance from singleton cache: It might have been put there
      // eagerly by the creation process, to allow for circular reference resolution.
      // Also remove any beans that received a temporary reference to the bean.
      destroySingleton(beanName);
      throw ex;
    }
  });
  bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// prototype类型
else if (mbd.isPrototype()) {
  // It's a prototype -> create a new instance.
  Object prototypeInstance = null;
  try {
    beforePrototypeCreation(beanName);
    prototypeInstance = createBean(beanName, mbd, args);
  }
  finally {
    afterPrototypeCreation(beanName);
  }
  bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
// 其他类型
else {
  String scopeName = mbd.getScope();
  if (!StringUtils.hasLength(scopeName)) {
    throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
  }
  Scope scope = this.scopes.get(scopeName);
  if (scope == null) {
    throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
  }
  try {
    Object scopedInstance = scope.get(beanName, () -> {
      beforePrototypeCreation(beanName);
      try {
        return createBean(beanName, mbd, args);
      }
      finally {
        afterPrototypeCreation(beanName);
      }
    });
    bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
  }
  catch (IllegalStateException ex) {
    throw new BeanCreationException(beanName,
        "Scope '" + scopeName + "' is not active for the current thread; consider " +
        "defining a scoped proxy for this bean if you intend to refer to it from a singleton",
        ex);
  }
}

值得注意的是构建实例前,spring都会在缓存里标记该bean正常构建中。

比如单例类型在构建前会调用beforeSingletonCreation(beanName),该方法会把该beanName加入缓存中。在前一个章节 《检查缓存的单例》 中会使用这里的缓存来判断这个bean是否处于单例构建状态。注意这个方法,如果缓存里边已存在该bean,会抛出BeanCurrentlyInCreationException异常。这个异常正是在构造器循环依赖(单例)的时候抛出。

protected void beforeSingletonCreation(String beanName) {
  if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
  }
}

而prototype以及其他类型调用的是beforePrototypeCreation(beanName),同样也是将beanName加入缓存:

protected void beforePrototypeCreation(String beanName) {
  Object curVal = this.prototypesCurrentlyInCreation.get();
  if (curVal == null) {
    this.prototypesCurrentlyInCreation.set(beanName);
  }
  else if (curVal instanceof String) {
    Set<String> beanNameSet = new HashSet<>(2);
    beanNameSet.add((String) curVal);
    beanNameSet.add(beanName);
    this.prototypesCurrentlyInCreation.set(beanNameSet);
  }
  else {
    Set<String> beanNameSet = (Set<String>) curVal;
    beanNameSet.add(beanName);
  }
}

这里用到的缓存prototypesCurrentlyInCreation,也正好呼应了是前一章 《检查prototype循环依赖》

其他类型的bean和prototype类型相同。

因此,spring在构建bean之前都会在缓存里标记该bean处于构建中,所以当发生循环依赖的时候可以快速感知到。

单例对象的早期引用

接上文,如果是单例对象,会调用匿名函数新建createBean进行单例bean构建:

if (mbd.isSingleton()) {
  sharedInstance = getSingleton(beanName, () -> {
    try {
      return createBean(beanName, mbd, args);
    }
    catch (BeansException ex) {
      // Explicitly remove instance from singleton cache: It might have been put there
      // eagerly by the creation process, to allow for circular reference resolution.
      // Also remove any beans that received a temporary reference to the bean.
      destroySingleton(beanName);
      throw ex;
    }
  });
}

createBean只是做一些构造前的准备,实际的构建过程在doCreateBean中。这很符合spring一贯的命名风格:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
  /// ...
  if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
  Object bean = instanceWrapper.getWrappedInstance();
  /// ...

  // Eagerly cache singletons to be able to resolve circular references
  // even when triggered by lifecycle interfaces like BeanFactoryAware.
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
      isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
      logger.trace("Eagerly caching bean '" + beanName +
          "' to allow for resolving potential circular references");
    }
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }

  // Initialize the bean instance.
  Object exposedObject = bean;
  try {
    populateBean(beanName, mbd, instanceWrapper);
    exposedObject = initializeBean(beanName, exposedObject, mbd);
  }

  /// ...
}

doCreateBean比较长,上边只保留核心代码。可以看到主要包含三个步骤:

  • 1.调用createBeanInstance初始化一个半成品的bean,该方法中会寻找满足条件的构造函数进行bean的初始化;
  • 2.如果是构建中的单例bean,并且允许循环依赖(默认允许),那么就会尝试调用addSingletonFactory方法提前曝光一个早期引用(也就是一个半成品bean)到缓存中;
  • 3.调用populateBean、initializeBean,将半成品的bean进一步初始化,主要是处理属性注入等等;

扼要地讲,步骤一处理了构造器注入;步骤二进行了半成品bean的早期引用曝光;步骤三进行了属性注入!

我们来看看早期引用曝光的逻辑:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
  Assert.notNull(singletonFactory, "Singleton factory must not be null");
  synchronized (this.singletonObjects) {
    if (!this.singletonObjects.containsKey(beanName)) {
      this.singletonFactories.put(beanName, singletonFactory);
      this.earlySingletonObjects.remove(beanName);
      this.registeredSingletons.add(beanName);
    }
  }
}

我们看到singletonFactories中会缓存一个工厂对象ObjectFactory,而这个ObjectFactory的getObject方法正是可以返回一个处于构建中半成品bean对象引用。

我们在前边已经讲到,单例对象构建中如果发现出现循环依赖,会尝试从单例的工厂对象中获得一个早期bean对象,并返回,从而避免循环依赖出现死循环。我们再来回忆下:

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 从singletonObjects获取已经初始化完毕的单例对象
  Object singletonObject = this.singletonObjects.get(beanName);
  // 判断该bean是不是正在初始化的单例bean
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    synchronized (this.singletonObjects) {
      // 从earlySingletonObjects中获取单例bean
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // 从singletonFactories缓存里获取单例工厂对象
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
          // 通过单例工厂对象中获取早期单例bean对象,并缓存到earlySingletonObjects中
          singletonObject = singletonFactory.getObject();
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  return singletonObject;
}

因此看到这里你就应该明白,单例对象的构造器注入没有进行早期引用曝光,所以是不能实现循环依赖的!但是单例对象的属性注入是先进行了早期引用曝光,在进行属性注入,所以是支持循环依赖的!

本文主要是探讨spring对循环依赖的处理策略和实现原理。我们认识到spring只支持单例对象的属性注入循环依赖,而其他类型的循环依赖都不支持。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK