云樾
踏浪而来
云樾
Spring Bean如何解决循环依赖问题

什么是循环依赖?

何为循环依赖?

Spring 循环依赖的场景有两种:

  1. 构造器的循环依赖。
  2. field 属性的循环依赖。

先上结果:Spring只解决单例下的循环依赖,且只解决field 属性的循环依赖!

构造器的循环依赖无解,只能抛出 BeanCurrentlyInCreationException 异常。(为啥构造器循环依赖无解?后面思考有)

如何解决?

答案就是Spring使用了三级缓存,其中第三级缓存缓存了只实例化但还没有实例化/属性填充的Bean对象!!

首先,要明确spring单例对象的初始化大概分三步:

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
  3. initializeBean:调用spring xml中的init方法。

我们从加载Bean最初的位置,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;
}
  1. 第一个步骤的getSingleton()方法的作用是尝试从三级缓存中获取目标对象,如果没有获取到,则尝试获取半成品的目标对象(仅实例化);如果第一个步骤没有获取到目标对象的实例,那么就进入第二个步骤。
  2. 第二个步骤的getSingleton()方法的作用是尝试创建目标对象,并且为该对象注入其所依赖的属性。

所以其实获取Bean对象也是先分两步走:一、查缓存 二、再走创建

我们先看一下第一个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;
}

这里就体现了Spring为解决循环依赖问题所设立的三级缓存

// DefaultSingletonBeanRegistry.java

/**
 * Cache of singleton objects: bean name to bean instance.
 *
 * 存放的是单例 bean 的映射。
 *
 * 对应关系为 bean name --> bean instance
 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/**
 * Cache of singleton factories: bean name to ObjectFactory.
 *
 * 存放的是【早期】的单例 bean 的映射。
 *
 * 对应关系也是 bean name --> bean instance。
 *
 * 它与 {@link #singletonObjects} 的区别区别在,于 earlySingletonObjects 中存放的 bean 不一定是完整的。
 *
 * 从 {@link #getSingleton(String)} 方法中,中我们可以了解,bean 在创建过程中就已经加入到 earlySingletonObjects 中了,
 * 所以当在 bean 的创建过程中就可以通过 getBean() 方法获取。
 * 这个 Map 也是解决【循环依赖】的关键所在。
 **/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/**
 * Cache of early singleton objects: bean name to bean instance.
 *
 * 存放的是 ObjectFactory 的映射,可以理解为创建单例 bean 的 factory 。
 *
 * 对应关系是 bean name --> ObjectFactory
 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

在加载Bean时,会依次从上面三个缓存中查找并加载,三个缓存分别存有以下三种类型的Bean对象:

  1. singletonObjects:完整的,属性装配完毕,完成初始化的Bean (一级缓存)
  2. earlySingletonObjects :完成实例化但是尚未初始化的,提前暴光的单例对象的Cache (二级缓存)
  3. singletonFactories:进入实例化阶段的单例对象工厂的cache (三级缓存)

那么问题来了,是在哪儿将这三种类型的Bean存入缓存的呢?

首先可以知道的是二级缓存earlySingletonObjects,当在三级缓存中查找到数据时,它会被自动存入二级缓存,然后将其从三级缓存中删除。

好,关于三级缓存的存入,我们先看一下上面提到的第二个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;
}

这里的addSingletonFactory() 方法,会将已实例化但未属性装配、未初始化的Bean对象存入三级缓存singletonFactories中:

// DefaultSingletonBeanRegistry.java

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);
        }
    }
}

然后会执行populateBean 以及initializeBean 操作,其中populateBean 会递归调用doGetBean 方法,来装配其依赖的其他Bean!

那么一级缓存是在哪个地方存入的呢?

回到上面的org.springframework.beans.factory.support.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;
}

其中的第二个getSingleton

// AbstractBeanFactory.java

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) {
            //....
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            //.....
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

因为此时Bean以及初始化完毕了,所以他现在是一个完整的Bean,通过addSingleton 将其加入一级缓存即可。

// 可以看到,加入1级缓存后,把二三级都删了
protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
            this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
    }
}

总结

用一张图来形象描述下这个Bean初始化中处理循环依赖的过程:

Bean初始化过程中处理循环依赖的过程

总结一下:

  • Spring 在创建 bean 的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 缓存中)。
  • 这样,一旦下一个 bean 创建的时候需要依赖 bean ,而这个bean又没有初始化完成,则直接使用 ObjectFactory 的 #getObject() 方法来获取。

问题1: 为什么构造器的循坏依赖无法解决?

因为Spring存入三级缓存的未完全Bean对象必须是已经实例化的,而通过构造器装配在实例化这关就挂了,自然无法存入三级缓存。

问题2:为什么要采用三级缓存?

既然有了三级缓存了,为什么还要设计二级缓存呢?可能很多人觉得二级缓存是个鸡肋,可有可无,其实这是Spring大量使用缓存提高性能的一点体现。每次都通过工厂去拿,需要遍历所有的后置处理器、判断是否创建代理对象,而判断是否创建代理对象本身也是一个复杂耗时的过程。设计二级缓存避免再次调用调用getEarlyBeanReference方法,提高bean加载流程。只能说,Spring是个海洋。

问题3:缓存的生命周期?

  • 三级缓存

当earlySingletonExposure属性为true时,将beanFactory加入缓存;当通过getSingleton从三级缓存中取出实例化的原始bean时或者完成初始化后,并清除singletonFactories中bean的缓存。

  • 二级缓存

当earlySingletonExposure属性为true时,将beanFactory加入缓存,当通过getSingleton从三级缓存中取出实例化的原始bean时,此时,将获取的bean加入二级缓存。当完成bean初始化,将bean加入一级缓存后,清除二级缓存;

  • 一级缓存

当完成bean初始化,通过addSingleton将bean加入一级缓存singletonObjects中,并且这个缓存是常驻内存中的。

从上述分析可知,三级缓存和二级缓存是不共存的,且其在Spring完成初始化,都会被清除掉。

参考:

[1] 高频面试题:Spring 如何解决循环依赖?

[2] Spring系列:Spring循环依赖知多少?(不一样的深度分析)

[3] spring是如何解决循环依赖的?

首页      编程      Spring Bean如何解决循环依赖问题

云樾

文章作者

发表评论

textsms
account_circle
email

云樾

Spring Bean如何解决循环依赖问题
什么是循环依赖? Spring 循环依赖的场景有两种: 构造器的循环依赖。 field 属性的循环依赖。 先上结果:Spring只解决单例下的循环依赖,且只解决field 属性的循环依赖! 构造器的循…
扫描二维码继续阅读
2020-08-24