likes
comments
collection
share

Spring 源码阅读 19:如何 get 到一个 Bean?

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

基于 Spring Framework v5.2.6.RELEASE

前情提要

在之前的文章中,用 18 篇内容,通过阅读源码的方式,分析了 Spring 的 ApplicationContext 应用上下文初始化的原理。在 Spring 框架的核心中,与上下文初始化同样重要的就是 Bean 的创建、初始化和获取的过程,这篇开始以 BeanFactory 接口中的getBean方法为切入点,开启这部分的源码阅读和原理分析。

getBean方法

因为在分析应用上下文初始化的时候,是以 ClassPathXmlApplicationContext 作为切入点的,因此,我们还是从这个类入手,找到getBean方法的实现,在它的父类 AbstractApplicationContext 中。

@Override
public Object getBean(String name) throws BeansException {
   assertBeanFactoryActive();
   return getBeanFactory().getBean(name);
}
// org.springframework.context.support.AbstractApplicationContext#getBean(java.lang.String)
@Override
public Object getBean(String name) throws BeansException {
   assertBeanFactoryActive();
   return getBeanFactory().getBean(name);
}

在方法体中,先调用了assertBeanFactoryActive方法,判断 BeanFactory 的状态,然后获取到当前上下文内的 BeanFactory,并把getBean的工作委派给它。

先看assertBeanFactoryActive方法做了什么。

// org.springframework.context.support.AbstractApplicationContext#assertBeanFactoryActive
protected void assertBeanFactoryActive() {
   if (!this.active.get()) {
      if (this.closed.get()) {
         throw new IllegalStateException(getDisplayName() + " has been closed already");
      }
      else {
         throw new IllegalStateException(getDisplayName() + " has not been refreshed yet");
      }
   }
}

可以看到,这里对上下文的状态做了判断,这里的activeclosed在之前的代码分析中曾经见过。AbstractApplicationContext 的refresh方法中,初始化上下文的第一步是通过调用prepareRefresh方法初始化上下文信息,这个方法中有两行代码,正好与这里的判断相呼应。

this.closed.set(false);
this.active.set(true);

上下文初始化时如何设置状态,可以参考这里:

Spring 源码阅读 03:初始化 Spring 上下文信息

接下来看getBean方法的委派。在 Spring 上下文初始化的过程中,创建的 BeanFactory 是 DefaultListableBeanFactory 类型,因此,这里获取到的上下文内部的 BeanFactory 也是这个类型,我们找到它的getBean方法,在 AbstractBeanFactory 中。

// org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)
@Override
public Object getBean(String name) throws BeansException {
   return doGetBean(name, null, null, false);
}

这里直接调用了doGetBean方法,根据 Spring 源码的命名习惯,看到方法名是doXXX就说明我们接近核心流程了。

Spring 源码阅读 19:如何 get 到一个 Bean?

代码量惊人,我直接贴图了,否则会有凑字数的嫌疑。

doGetBean方法

接下来的重点,就来分析这个方法的源码。不过在此之前,还需要简单回顾一下之前的内容,结合 Spring 上下文初始化的过程,明确接下来代码分析的重点。

前提

在 Spring 上下文初始化的过程中,创建完 BeanFactory 之后,会预先创建并初始化一些 Bean,这些就是配置文件中定义的不允许懒加载的单例 Bean,因此,当我们分析getBean方法相关流程的时候,这些 Bean 是已经创建好的,如何获取已经创建好的 Bean 是分析的重点之一。

除此之外,还有一些非单例的 Bean,每次获取的时候,都会创建一个新的 Bean 实例,Spring 中获取 Bean 的过程中大部分逻辑都与它们无关,因此这部分不会做为重点。

最后就是那些需要懒加载的单例 Bean,它们会在第一次通过getBean方法获取的时候才会被创建和初始化,涉及到这部分的时候,会重点分析分析这些 Bean 是如何被初始化的。在之前的 Spring 上下文初始化的源码分析中,随着上下文一起被初始化的非懒加载的单例 Bean 也是通过getBean方法进行创建和初始化的,当时并没有分析他们是如何被创建的,这里也算是补充上了。

言归正传,开始分析源码,下面的内容可以对照源码来看。

Bean 的名称

方法开头,根据参数中提供的 Bean 名称,通过transformedBeanName方法获取到一个beanName。后续很多方法调用使用的都是这个beanName,这里的逻辑和作用我们之后分析。

获取已经初始化好的 Bean

然后会通过getSingleton方法,获取已经进入单例缓存中已经创建好的 Bean 实例。这里会涉及到 Spring 容器对单例 Bean 的循环依赖的处理方案,也就是三级缓存。如果这里能够获取到 Bean 的实例,之后会经过getObjectForBeanInstance方法的处理,得到最终的 Bean 实例并返回。需要注意的是,这里获取 Bean 的过程,都是基于 Bean 在之前已经初始化的情况。如果 Bean 没有在之前被初始化过,那么getSingleton方法会回的到空值,程序会进入else语句块。

获取未被初始化的 Bean

在方法体最外层的else语句块中,是这个 Bean 第一次通过getBean方法被获取,此时,Bean 的创建和初始化过程也会在这里完成。

首先会根据beanName判断 Bean 是不是一个正在创建的原型 Bean(也就是非单例 Bean),就抛出异常。

然后获取到当前容器的父容器,如果当前容器中没有beanName对应的 BeanDefinition,就从父容器中寻找。方法也是通过调用父容器的getBeandoGetBean方法。因为 Spring 初始化上下文时,不管是 ApplicationContext 还是内置的容器 BeanFactory,它们的父容器都是null,所以这里的逻辑不会走到。

再下来,通过markBeanAsCreated方法,标记 Bean 已经开始创建了。看来要开始进入创建 Bean 实例最主要的流程了,也就是之后的try语句块的内容。大致浏览一下这部分代码,跟获取已经初始化好的 Bean 一样,也是先获取到一个sharedInstance,再经过getObjectForBeanInstance方法的处理,得到最终的结果并返回。

Bean 实例的创建

开始分析实例创建的过程。首先会根据beanName获取到对应的 BeanDefinition,因为 Bean 的配置信息都在 BeanDefinition 里,所以 BeanDefinition 在这里是必需的。不过,这里获取到的类型是 RootBeanDefinition,这里先留下疑问,之后深入分析的时候再看为什么需要这样。

接下来,通过getBean方法,触发当前 Bean 依赖的 Bean 进行初始化。

完成之后,根据 Bean 的作用与类型,分开不同的逻辑来处理,不过,这里都可以看到一个名为createBean的方法,很明显,Bean 的实例是在这里创建的。

总结

这篇主要就分析到这里,因为getBean的流程并不像 Spring 上下文初始化的流程那样有清晰的步骤体现在源码中,因此,这里先通过一个大概的流程介绍,帮助你了解全局。之后,会重点围绕doGetBean方法,对这一流程中重要的部分进行深入分析,敬请期待。

转载自:https://juejin.cn/post/7136230337247444999
评论
请登录