携手创造,一起成长!这是我参加「日新计划 8 月更文应战」的第33天,点击检查活动概况

基于 Spring Framework v5.2.6.RELEASE

接上篇:Spring 源码阅读 20:获取 Bean 的标准称号

前情提要

上一篇介绍了获取 Bean 实例目标的第一步,依据给定的name获取 Bean 的标准称号。给定的name或许是 Bean 的别号或者 FactoryBean 的逆向引证称号,获取到的标准称号是 Bean 在容器中的唯一标识符。

本文接着介绍 AbstractBeanFactory 中doGetBean方法的下一行要害代码。

Object sharedInstance = getSingleton(beanName);

从缓存中获取 Bean 实例目标

直接进入getSingleton方法的源码。

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String)
@Override
@Nullable
public Object getSingleton(String beanName) {
   return getSingleton(beanName, true);
}
// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

这个方法的作用是依据beanName从容器缓存中获取对应的 Bean 实例目标。此处有两个需求留意的当地:

  1. 调用方法传入的参数,是经过transformedBeanName方法获取的标准的 Bean 称号。
  2. 经过调用重载方法,给了allowEarlyReference参数一个默认值true

然后检查方法体中的代码,首要,大概浏览一下方法体的整个流程,能够发现,在容器中有三个调集,Spring 会运用beanName顺次从这三个调集中查找 Bean 的实例目标,这三个调集,咱们能够将之看作缓存(实践上便是缓存),在 DefaultSingletonBeanRegistry 中能够找到创立它们的代码:

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

这三个 Map 调集的 Key 都是 Bean 的称号,Value 分别是单例 Bean 的实例、前期单例 Bean 的实例、单例 Bean 工厂。

getSingleton方法的最初,会测验从singletonObjects调集获取 Bean 的实例,获取到就得到了想要的成果,直接返回。

当没有获取到时,假如isSingletonCurrentlyInCreation(beanName)的成果为true,那么测验从下一个缓存 Map 中获取。isSingletonCurrentlyInCreation方法的源码如下:

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isSingletonCurrentlyInCreation
/**
* Return whether the specified singleton bean is currently in creation
* (within the entire factory).
*  @param  beanName the name of the bean
*/
public boolean isSingletonCurrentlyInCreation(String beanName) {
   return this.singletonsCurrentlyInCreation.contains(beanName);
}

这儿还是一个调集,依据注释能够知道,这个方法的作用是判别一个单例 Bean 是不是正在被创立,也便是说这个调集中保存了一切正在被创立的 Bean 的称号。

返回到getSingleton方法中,假如从singletonObjects中获取不到,而且这个 Bean 正在被创立,那么就测验从earlySingletonObjects调集中获取。这儿寄存的是前期单例 Bean 的实例目标,这些 Bean 实例被创立好,可是还没有进行完初始化,就会被放在这个调集傍边,能够提早被获取到了,假如这儿获取到了目标 Bean 实例,那么也会作为方法的成果返回。

假如还没有获取到,而且allowEarlyReferencetrue,那么会接着测验从singletonFactories中获取,这儿缓存的是创立前期单例的 Bean 工厂,假如从singletonFactories调集中获取到了这个工厂目标,那么就调用它的getObject方法,将前期 Bean 创立出来,并作为成果返回。

别的,假如前期 Bean 是在这儿创立的,那么还需求把创立好的前期 Bean 添加到earlySingletonObjects中,并把工厂从singletonFactories移除掉,由于再次之后,它已经不会被用到了。

你或许会问了:何必呢?直接把 Bean 完整地初始化好放在一个缓存中获取,获取不到就创立新的,不就行了吗,为什么还需求前期实例和工厂实例这种中间状态呢?

这就要讲到循环依靠的问题。

循环依靠

假设接下来咱们说到的类型,都是用来创立单例 Bean 的类型,思考这样三种状况:

  1. ClassA 中有一个特点的类型是 ClassA
  2. ClassA 中有一个特点的类型是 ClassB,一起,ClassB 中有一个特点的类型是 ClassA
  3. ClassA 中有一个特点的类型是 ClassB,ClassB 中有一个特点的类型是 ClassC,一起,ClassC 中有一个特点的类型是 ClassA

在之前的 Spring 源码阅读 19:如何 get 到一个 Bean? 一文中,从前介绍过,完结一个 Bean 的初始化之前,必需求先初始化其各个特点值对应的 Bean。当呈现以上三种状况或者类似的形成循环依靠联系的状况,就会呈现问题。

以第 3 种状况为例,要完结 ClassA 的初始化,就要先初始化 ClassB,那么就要先初始化 ClassC,初始化 ClassC 又需求完结 ClassA 的初始化。这样就变成了「鸡生蛋蛋生鸡」的问题。

这种状况在实践的开发中,难以彻底避免,因而,这是 Spring 必需求处理的问题。

三级缓存

Spring 处理这个问题的方法便是:在一个 Bean 还没有完结初始化的时候,就暴露它的引证。这儿就需求用到前面介绍过的三个缓存 Map,也叫三级缓存

以刚刚描绘的状况为例,当 ClassA 需求需求一个 ClassB 的单例 Bean 作为特点填充的时候,先创立一个前期的 ClassB 的 Bean,此时,ClassB 刚刚被创立出来,还没有进行特点填充等初始化的流程,就将它放在earlySingletonObjects中,这样,ClassA 的 Bean 实例就能够运用这个 ClassB 的前期实例进行特点填充,ClassB 的前期实例,能够再次之后再进行初始化,这样就不会由于循环依靠联系,导致无法初始化这三个 Bean。

暂时无法在飞书文档外展现此内容

上图是 Bean 的初始化流程和从各缓存中获取 Bean 实例的流程,具体的过程,会在之后剖析到第一次获取一个没有被创立的 Bean 的流程时,再经过代码做深入剖析。

限制

不过,Spring 对这个问题的处理还存在限制性。

在 Spring 中,能够经过 setter 方法和结构器将一个 Bean 实例作为特点值注入给另一个 Bean 实例。依据 Spring 经过三级缓存处理循环依靠问题的原理,在一个 Bean 被放入缓存之前,至少需求先经过反射把目标创立出来。可是,假如一个一个特点只能经过结构器注入,那么就无法在注入之前,完结 Bean 目标的创立,也就无法处理循环依靠的问题。

因而,Spring 只能处理经过 setter 注入的循环依靠问题。

总结

本文剖析了 Spring 容器从缓存中获取单例 Bean 的原理,主要涉及到了 Spring 为处理循环依靠问题,而设计的三级缓存机制。经过getSingleton方法从缓存中获取 Bean 实例目标,或许获取到,也或许由于 Bean 还彻底没有开端创立而获取不到。下一篇,咱们接着剖析,假如这儿获取到了单例 Bean,接下来还需求做哪些处理。