likes
comments
collection
share

Spring 源码阅读 23:创建 Bean 实例前的准备工作

作者站长头像
站长
· 阅读数 12

基于 Spring Framework v5.2.6.RELEASE

接上篇:Spring 源码阅读 22:使用 FactoryBean 创建 Bean 实例对象

前情提要

之前用 3 篇文章,通过阅读源码,分析了通过 Spring 容器的getBean方法,获取 Bean 实例的第一种情况,也就是,在单例实例已经被创建的情况下,如何从容器中获取到 Bean 的实例对象或者其对应的 FactoryBean 对象。接下来,将分析另外一种情况,就是,当 Spring 的容器缓存中找不到我们想要的 Bean 实例的时候,Spring 怎样把这个实例创建出来。这一部分比上一种情况更复杂,因此也会通过多篇文章来深入分析。

为保证连贯性,希望你从 Spring Framework 源码解读 专栏中的第 19 篇开始阅读。

在 AbstractBeanFactory 的doGetBean方法中,如果无法从容器缓存中获取到 Bean 实例对象,那么,代码流程将会走进第一个else语句块,之后所有的流程,都在这个语句块中。下面开始按照代码流程逐步分析。

检查 Prototype Bean 的创建状态

首先,会有一个if语句判断beanName对应的 Bean 是不是一个原型 Bean 且正在被创建,如果是,则抛出异常。

if (isPrototypeCurrentlyInCreation(beanName)) {
   throw new BeanCurrentlyInCreationException(beanName);
}

这里的isPrototypeCurrentlyInCreation方法跟之前见到过的isSingletonCurrentlyInCreation方法很类似,其实就是判断beanName是不是在一个集合中存在,这个集合中缓存着正在被创建的原型 Bean 的名称。

// org.springframework.beans.factory.support.AbstractBeanFactory#isPrototypeCurrentlyInCreation
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
   Object curVal = this.prototypesCurrentlyInCreation.get();
   return (curVal != null &&
         (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}

但是与单例的 Bean 在整个容器中只存在一个实例对象不同,原型 Bean 在每次通过getBean方法获取 Bean 实例的时候,都会创建一个新的实例,因此,不存在从容器缓存中获取的情况,因此,代码执行到这里如果 Bean 的实例正在被创建的话,直接报错即可。

从父容器中获取 Bean 实例对象

接下来,Spring 尝试从父容器中获取 Bean 的实例对象。

// Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
   // Not found -> check parent.
   String nameToLookup = originalBeanName(name);
   if (parentBeanFactory instanceof AbstractBeanFactory) {
      return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
            nameToLookup, requiredType, args, typeCheckOnly);
   }
   else if (args != null) {
      // Delegation to parent with explicit args.
      return (T) parentBeanFactory.getBean(nameToLookup, args);
   }
   else if (requiredType != null) {
      // No args -> delegate to standard getBean method.
      return parentBeanFactory.getBean(nameToLookup, requiredType);
   }
   else {
      return (T) parentBeanFactory.getBean(nameToLookup);
   }
}

如果当前的容器存在父容器,并且当前容器不存在beanName对应的 BeanDefinition,那么,会尝试从父容器中获取 Bean 实例。这里要留意,虽然这一段逻辑相对靠前,但是,根据判断可以知道,只有在当前容器不存在相应的 BeanDefinition 的情况下,才会尝试从高父容器获取实例。

按照之前的源码分析,默认情况下,Spring 在初始化容器的时候,是没有父容器的,因此,不会进入这段逻辑。不过我们凭着兴趣也看看这里面的逻辑。

在if语句块的开头,调用了一个originalBeanName的方法,并且注意,这里传入的参数是name而不是beanName,也就是这里传入了调用getBean方法时提供的名称,而不是经过转换的规范名称。进入方法,看看这里做了什么。

// org.springframework.beans.factory.support.AbstractBeanFactory#originalBeanName
protected String originalBeanName(String name) {
   String beanName = transformedBeanName(name);
   if (name.startsWith(FACTORY_BEAN_PREFIX)) {
      beanName = FACTORY_BEAN_PREFIX + beanName;
   }
   return beanName;
}

这里首先调用transformedBeanName方法,通过name获取到了规范名称beanName,这段逻辑我们之前已经了解过了。接着,如果name是以&符号开头的,那就给beanName也加上&前缀。

因为name的值有可能是 Bean 的别名,因此,如果去父容器中查找的话,需要使用 Bean 的唯一标识符,因此在这里进行了转换,又因为,名称是否包含&前缀表明了调用方是想获取 Bean 实例对象还是其对应的 FactoryBean 工厂实例,因此,&前缀要保持原样。

处理完名称之后,就尝试从父容器中获取,然后将获取的结果返回,因为是在当前容器没有beanName对应的 BeanDefinition 的情况下,才尝试从父容器获取,这里算是一个托底的步骤,所以,不管有没有获取到,都直接将结果返回。

标记创建

接着看后面的代码:

if (!typeCheckOnly) {
   markBeanAsCreated(beanName);
}

根据之前的代码分析,在调用doGetBean方法的时候,typeCheckOnly参数传入的值是false,因此这里会进入语句块,执行markBeanAsCreated方法。

// org.springframework.beans.factory.support.AbstractBeanFactory#markBeanAsCreated
/**
* Mark the specified bean as already created (or about to be created).
* <p>This allows the bean factory to optimize its caching for repeated
* creation of the specified bean.
*  @param  beanName the name of the bean
*/
protected void markBeanAsCreated(String beanName) {
   if (!this.alreadyCreated.contains(beanName)) {
      synchronized (this.mergedBeanDefinitions) {
         if (!this.alreadyCreated.contains(beanName)) {
            // Let the bean definition get re-merged now that we're actually creating
            // the bean... just in case some of its metadata changed in the meantime.
            clearMergedBeanDefinition(beanName);
            this.alreadyCreated.add(beanName);
         }
      }
   }
}

这个方法的作用就是,标记当前beanName对应的 Bean 实例已经被创建或者即将被创建。是一项性能优化,也算是在真正创建 Bean 之前执行的准备工作。

获取 BeanDefinition

接下来进入了一个try语句块,接着看里面的代码:

final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);

这里获取了 BeanDefinition,看来创建 Bean 实例对象的工作就快要开始了。不过,可以注意到,这里获取到的 BeanDefinition 的类型是 RootBeanDefinition,而不是容器初始化时创建的 GenericBeanDefinition。

关于几个重要的 BeanDefinition 实现类,之前的文章中做过简单介绍,可以参考:Spring 源码阅读 11:BeanDefinition 介绍

这里来解释一下为什么会获取一个 RootBeanDefinition,在配置文件中定义 Bean 的信息的时候,Bean 之间是可以存在父子关系的。具有父子关系的 BeanDefinition 在实际使用之前,Spring 会将它们的信息进行合并,得到 RootBeanDefinition,这里的方法getMergedLocalBeanDefinition获取到的就是合并后的 BeanDefinition。

进入方法查看源码:

// org.springframework.beans.factory.support.AbstractBeanFactory#getMergedLocalBeanDefinition
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
   // Quick check on the concurrent map first, with minimal locking.
   RootBeanDefinition mbd = this.mergedBeanDefinitions.get(beanName);
   if (mbd != null && !mbd.stale) {
      return mbd;
   }
   return getMergedBeanDefinition(beanName, getBeanDefinition(beanName));
}

首先会从mergedBeanDefinitions集合中获取beanName对应的 RootBeanDefinition,如果不存在的话,就调用另外一个重载方法进行 BeanDefinition 的合并,最后返回 RootBeanDefinition 类型的结果。

具有父子关系的 Bean 在实际开发中很少见,合并的流程也并不复杂,而且与我们分析的主线流程关系也不大,因此这里不做深入分析了,你可以自行查看。如果我们配置的 Bean 没有 Parent Bean,那么可以简单理解为使用beanName对应的 GenericBeanDefinition 中的信息创建了一个 RootBeanDefinition。

在得到 RootBeanDefinition 之后,调用了checkMergedBeanDefinition方法。

// org.springframework.beans.factory.support.AbstractBeanFactory#checkMergedBeanDefinition
protected void checkMergedBeanDefinition(RootBeanDefinition mbd, String beanName, @Nullable Object[] args)
      throws BeanDefinitionStoreException {

   if (mbd.isAbstract()) {
      throw new BeanIsAbstractException(beanName);
   }
}

这里就是一个检查,确保它不是一个无法实例化的抽象的 BeanDefinition。

初始化依赖的 Bean

如果之前的检查没有问题,那么,现在已经有了一个可用的 BeanDefinition。不过,在真正初始化 Bean 实例对象之前,还需要实例化它依赖的 Bean 实例。

// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
   for (String dep : dependsOn) {
      if (isDependent(beanName, dep)) {
         throw new BeanCreationException(mbd.getResourceDescription(), beanName,
               "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
      }
      registerDependentBean(dep, beanName);
      try {
         getBean(dep);
      }
      catch (NoSuchBeanDefinitionException ex) {
         throw new BeanCreationException(mbd.getResourceDescription(), beanName,
               "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
      }
   }
}

首先会从 BeanDefinition 中获取到所有的依赖 Bean 的名称,然后进行遍历,对每个依赖 Bean 的名称执行如下操作:

  1. 通过isDependent方法,判断被依赖 Bean,并不依赖当前的要创建的 Bean,也就是它们没有循环的依赖关系。
  2. 通过registerDependentBean方法,在容器中注册它们的依赖关系。
  3. 通过getBean方法初始化依赖的 Bean。

接下来分别看这几个步骤。先看isDependent方法。

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isDependent(java.lang.String, java.lang.String)
protected boolean isDependent(String beanName, String dependentBeanName) {
   synchronized (this.dependentBeanMap) {
      return isDependent(beanName, dependentBeanName, null);
   }
}

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isDependent(java.lang.String, java.lang.String, java.util.Set<java.lang.String>)
private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) {
   if (alreadySeen != null && alreadySeen.contains(beanName)) {
      return false;
   }
   String canonicalName = canonicalName(beanName);
   Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
   if (dependentBeans == null) {
      return false;
   }
   if (dependentBeans.contains(dependentBeanName)) {
      return true;
   }
   for (String transitiveDependency : dependentBeans) {
      if (alreadySeen == null) {
         alreadySeen = new HashSet<>();
      }
      alreadySeen.add(beanName);
      if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
         return true;
      }
   }
   return false;
}

这里的判断逻辑并不难理解,不仅判断了直接依赖的情况,还判断了简介依赖的情况。判断的依据是容器中的dependentBeanMap集合,定义如下:

 /** Map between dependent bean names: bean name to Set of dependent bean names. */
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);

判断无误之后,在容器中注册依赖关系的方法registerDependentBean代码如下:

// org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#registerDependentBean
public void registerDependentBean(String beanName, String dependentBeanName) {
   String canonicalName = canonicalName(beanName);

   synchronized (this.dependentBeanMap) {
      Set<String> dependentBeans =
            this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
      if (!dependentBeans.add(dependentBeanName)) {
         return;
      }
   }

   synchronized (this.dependenciesForBeanMap) {
      Set<String> dependenciesForBean =
            this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
      dependenciesForBean.add(canonicalName);
   }
}

向容器中的dependentBeanMapdependenciesForBeanMap中添加了依赖关系,注意这两个集合的定义:

 /** Map between dependent bean names: bean name to Set of dependent bean names. */
private final Map<String, Set<String>> dependentBeanMap = new ConcurrentHashMap<>(64);

/** Map between depending bean names: bean name to Set of bean names for the bean's dependencies. */
private final Map<String, Set<String>> dependenciesForBeanMap = new ConcurrentHashMap<>(64);

它们两者的区别就是,Key-Value 映射关系是相反的,应该是为了方便根据依赖关系相互查找。

这里为什么存在依赖关系会抛出异常呢?我的理解是这样的:这两个集合当中保存的依赖关系,是在创建 Bean 实例的过程中添加的,如果当前要创建的 Bean 和所以来的 Bean 存在循环依赖的关系,并且dependentBeanMap集合中存在它们的依赖关系,那么,当前要创建的 Bean 的实例应该是已经开始被创建的状态才对,因此这里会报错。

之后,就是调用getBean创建以来的 Bean 实例了,这里是一个递归调用,执行的就是我们现在正在分析的流程。

总结

本文先分析到这里,主要是创建 Bean 实例对象之前的一些准备工作,接下来将进入创建 Bean 实例的部分。下篇文章见。