Spring 循环依赖处理

简介: 什么是循环依赖?如果对象A使用到了对象B,那么我们就说对象A依赖对象B。如果对象A依赖对象B的同时,对象B也依赖对象A,我们就说对象A和对象B产生了循环依赖。

什么是循环依赖?


如果对象A使用到了对象B,那么我们就说对象A依赖对象B。如果对象A依赖对象B的同时,对象B也依赖对象A,我们就说对象A和对象B产生了循环依赖。


Spring 如何处理循环依赖


前面的文章 《Java 面试必备的 Spring Bean 生命周期总结》 中我们有提到,Spring bean 的生命周期包含了元数据解析、实例化、初始化、销毁等阶段,而 Spring bean 在初始化结束才完成 bean 的创建。如果创建 bean 的构造方法或者工厂方法参数产生了循环依赖,Spring 由于无法拿到 bean 的实例无法处理,创建对象A时依赖对象B,然后去创建对象B,创建对象B时又依赖对象A,然后又去创建对象A,然后就 A->B->A 开启无限循环模式。由于 Spring bean 实例化后即可拿到 bean 的实例对象,在初始化阶段设置依赖对象时 Spring 可以把前面创建的 bean 实例设置到依赖的引用中,因此 spring 可以在初始化阶段处理循环依赖,而不必等到 bean 完全创建结束。值得留意的是 Spring 只会对单例 bean 进行循环依赖处理,而其他作用域的 bean Spring 则不予处理。


Spring 循环依赖处理源码分析


Spring 依赖的处理在于创建 bean 实例后获取依赖的 bean,因此代码分析时可以从创建 bean 的代码入手,而创建 bean 则隐藏于获取 bean 的方法中,Spring 获取 bean 的各重载方法 BeanFactory#getBean 实现最终都会调用方法 AbstractBeanFactory#doGetBean,其方法的整体结构如下。


  protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    // 先去除前缀&获取别名对应的真实名称
    final String beanName = transformedBeanName(name);
    Object bean;
    // 先从缓存中获取单例 bean
    // getSingleton方法可能获取到未初始化结束的bean,这是由于循环引用导致
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
      if (logger.isTraceEnabled()) {
        //beanName对应的singleton正在创建
        if (isSingletonCurrentlyInCreation(beanName)) {
          logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
              "' that is not fully initialized yet - a consequence of a circular reference");
        } else {
          logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
        }
      }
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    } else {
      // 只要参数 args 不为空,就需要重新获取
      ... 省略部分重新获取 bean 的代码
    }
    // 获取的bean和给定的类型不匹配,进行类型转换
    if (requiredType != null && !requiredType.isInstance(bean)) {
      ... 省略部分类型转换的代码
    }
    return (T) bean;
  }


可以看到,Spring 首先获取缓存的单例 bean,并且获取到的有可能是未创建结束的 bean,如果未获取到则会重新获取,最后如果类型不匹配还会进行类型转换。那么就先从获取缓存的单例 bean 的方法 getInstance 入手,跟踪其源码,发现其调用了方法 DefaultSingletonBeanRegistry#getSingleton(String, boolean),源码如下。


public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
  /**
   * 一级缓存,存放初始化结束的bean
   */
  private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
  /**
   * 三级缓存,bean名称->获取未初始化结束bean的ObjectFactory
   */
  private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
  /**
   * 二级缓存,bean名称->未初始化结束的bean
   */
  private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
  @Nullable
  protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 先从一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    // A对象依赖B对象,B对象同时依赖A对象,则A对象初始化时会初始化B对象,B对象初始化时获取的A对象正在创建
    // A对象未初始化结束不会存到singletonObjects
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
        // 再从二级缓存获取
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
          // 再从三级缓存获取,并保存到二级缓存
          // 创建bean时未完成初始化的bean会调用#addSingletonFactory方法存放到singletonFactories
          ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
          if (singletonFactory != null) {
            singletonObject = singletonFactory.getObject();
            this.earlySingletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
          }
        }
      }
    }
    return singletonObject;
  }
}


可以看到,Spring 使用了三个缓存保存相关的 bean 信息,其中一级缓存保存创建完成的 bean,二级缓存保存未创建完成的bean,三级缓存保存了创建 bean 的 ObjectFactory。Spring 会依次尝试从一级、二级、三级缓存中获取 bean,从三级缓存获取到 bean 之后 Spring 会将其存入二级缓存中,然后删除三级缓存。代码看到这里,我们已经知道了二级缓存的数据来自三级缓存,而一级缓存和三级缓存的数据来源尚不清楚。这里先给出结论,当 bean 实例化结束,Spring 就会把获取早期 bean 实例的 ObjectFactory 存入三级缓存的 Map 中,而当单例 bean 创建结束,Spring 就会把单例 bean 实例存入一级缓存中,因此具体具体的源码还需要从创建 bean 的代码中看起。


在前面 doGetBean 方法的源码分析中,为了保证清晰的了解方法内部的实现结构,有意的忽略了 bean 的创建,当缓存的单例 bean 中无法查到需要的 bean 时就需要创建 bean,获取单例 bean 的代码如下。


  protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    // 先去除前缀&获取别名对应的真实名称
    final String beanName = transformedBeanName(name);
    Object bean;
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
      ... 省略从缓存获取单例 bean 的代码
    } else {
        ... 省略部分代码
        if (mbd.isSingleton()) {
          //获取 singleton bean
          sharedInstance = getSingleton(beanName, () -> {
            try {
              return createBean(beanName, mbd, args);
            } catch (BeansException ex) {
              destroySingleton(beanName);
              throw ex;
            }
          });
          bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
        } else if (mbd.isPrototype()) {
          ... 省略部分获取 prototype bean 的代码
        } else {
          ... 省略获取部分自定义作用域 bean 的代码
        }
      } catch (BeansException ex) {
        cleanupAfterBeanCreationFailure(beanName);
        throw ex;
      }
    }
    // 获取的bean和给定的类型不匹配,进行类型转换
    if (requiredType != null && !requiredType.isInstance(bean)) {
      ... 省略部分类型转换代码
    }
    return (T) bean;
  }


可以看到,获取单例 bean 调用了方法 DefaultSingletonBeanRegistry#getSingleton(String, ObjectFactory<?>),并且将createBean 方法封装到 ObjectFactory 中,我们先看 getSingleton 方法,源码如下。


  public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
      Object singletonObject = this.singletonObjects.get(beanName);
      if (singletonObject == null) {
        // 缓存中不存在 bean,开始创建 bean
        ... 省略部分代码,bean 在销毁时抛出异常
        // 创建之前将当前 bean 添加到正在创建的 bean 的集合中
        beforeSingletonCreation(beanName);
        boolean newSingleton = false;
        boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
        if (recordSuppressedExceptions) {
          this.suppressedExceptions = new LinkedHashSet<>();
        }
        try {
          // 开始获取 bean 实例
          singletonObject = singletonFactory.getObject();
          newSingleton = true;
        } catch (IllegalStateException ex) {
          singletonObject = this.singletonObjects.get(beanName);
          if (singletonObject == null) {
            throw ex;
          }
        } catch (BeanCreationException ex) {
          if (recordSuppressedExceptions) {
            //TODO 何时 this.suppressedExceptions.size() > 0 ?
            for (Exception suppressedException : this.suppressedExceptions) {
              ex.addRelatedCause(suppressedException);
            }
          }
          throw ex;
        } finally {
          if (recordSuppressedExceptions) {
            this.suppressedExceptions = null;
          }
          // 获取单例 bean 之后从当前正在创建
          afterSingletonCreation(beanName);
        }
        if (newSingleton) {
          // 创建单例 bean 结束,缓存单例 bean 到一级缓存
          addSingleton(beanName, singletonObject);
        }
      }
      return singletonObject;
    }
  }


这里首先使用的对象锁,首先从一级缓存中获取单例 bean,这是因为该方法有可能被外部调用,如果未获取到则会创建 bean,创建 bean 之前会将 bean 名称添加到正在创建的集合中,创建结束将创建的 bean 的名称从集合中移除,而创建则使用了 ObjectFactory,其内部实现为前面的代码调用该方法封装的 createBean 方法,如果是新创建的单例 bean 会调用 addSingleton 方法将其添加到一级缓存中,这里也印证了我们前面给出的结论。而三级缓存的数据则来自 createBean 方法,跟踪其源码发现其调用了 AbstractAutowireCapableBeanFactory#doCreateBean 方法控制真正的 bean 创建流程,doCreateBean 部分源码。


  protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {
    final Object bean = ... 省略创建 bean 的部分代码
    ... 省略部分代码
    // 是否允许引用未初始化结束的bean
    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");
      }
      // A,B bean循环依赖
      // 缓存未初始化完成的bean A,下面的populateBean方法对属性B赋值时获取bean B
      // B初始化时对依赖的属性A赋值,可以从缓存获取bean A
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    // Initialize the bean instance.
    Object exposedObject = bean;
    ... 省略部分初始化 bean 的代码
    ... 省略部分初始化 bean 的代码
    ... 省略部分注册销毁方法的代码
    return exposedObject;
  }


可以看到,Spring 创建单例 bean 之后发现如果允许暴露早期的引用,则会调用 addSingletonFactory 方法将其添加到三级缓存中,这里印证了前面实例化单例 bean 后保存到三级缓存的结论。


总结

Spring 使用了三级缓存保存了 bean 实例获取的相关信息。

一级缓存保存完全创建结束的 bean 的实例。

二级缓存保存未创建结束的早期 bean 的实例。

三级缓存保存了获取早期 bean 实例的 ObjectFactory。

Spring 在创建 bean 结束后将 bean 的实例添加到一级缓存中。

Spring 在实例化单例 bean 结束后将 ObjectFactory 缓存到 三级缓存中。

Spring 从缓存中获取单例 bean 时,依次尝试从一级、二级、三级缓存获取,从三级获取到缓存后然后再保存到二级缓存。


目录
相关文章
|
7天前
|
存储 缓存 Java
面试问Spring循环依赖?今天通过代码调试让你记住
该文章讨论了Spring框架中循环依赖的概念,并通过代码示例帮助读者理解这一概念。
面试问Spring循环依赖?今天通过代码调试让你记住
|
4天前
|
缓存 Java Spring
spring如何解决循环依赖
Spring框架处理循环依赖分为构造器循环依赖与setter循环依赖两种情况。构造器循环依赖不可解决,Spring会在检测到此类依赖时抛出`BeanCurrentlyInCreationException`异常。setter循环依赖则通过缓存机制解决:利用三级缓存系统,其中一级缓存`singletonObjects`存放已完成的单例Bean;二级缓存`earlySingletonObjects`存放实例化但未完成属性注入的Bean;三级缓存`singletonFactories`存放创建这些半成品Bean的工厂。
|
5天前
|
前端开发 Java 测试技术
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
|
30天前
|
缓存 Java 开发者
Spring循环依赖问题之Spring循环依赖如何解决
Spring循环依赖问题之Spring循环依赖如何解决
|
30天前
|
缓存 Java Spring
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
Spring循环依赖问题之Spring不支持构造器内的强依赖注入如何解决
|
30天前
|
Java Spring
Spring循环依赖问题之构造器内的循环依赖如何解决
Spring循环依赖问题之构造器内的循环依赖如何解决
|
30天前
|
Java Spring 容器
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
Spring循环依赖问题之两个不同的Bean A,导致抛出异常如何解决
|
30天前
|
存储 缓存 Java
Spring循环依赖问题之循环依赖异常如何解决
Spring循环依赖问题之循环依赖异常如何解决
|
30天前
|
XML Java 数据格式
循环依赖问题之创建Bean的过程中发生异常,Spring会如何处理
循环依赖问题之创建Bean的过程中发生异常,Spring会如何处理
|
存储 缓存 Java
Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?
在研究 『 Spring 是如何解决循环依赖的 』 的时候,了解到 Spring 是借助三级缓存来解决循环依赖的。
439 0