Spring 源码阅读 31:基于注解初始化 Spring 上下文的原理(1)

951 阅读7分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

基于 Spring Framework v5.2.6.RELEASE

概述

之前的 Spring 源码分析文章,都是基于 XML 配置文件初始化的 Spring 容器,虽然这种配置方式很少用了,但这是 Spring 最早的容器配置方式,对其原理的分析,能够最大程度地了解 Spring 容器的内部机制。从这篇开始,将开始分析 Spring 如何基于注解来初始化上下文。

基于注解的 Spring 上下文

有了之前对 Spring 如何基于 XML 注解来初始化上下文作为基础,基于注解来初始化上下文的原理分析可以与之对比来看,即使没有看过之前的文章,下文中涉及到的部分,我也会详细讲解。

在分析基于 XML 配置初始化 Spring 上下文的原理是,是以 ClassPathXmlApplicationContext 类型的构造方法作为入口的,比如下面的代码:

ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

执行后,会得到context对象,它就是初始化好的上下文对象,我们可以通过它来获取 Bean 实例,或者执行其他的操作。

如果要基于注解来初始化 Spring 的上下文,方法与之类似,只不过这里的上下文类型需要用到 AnnotationConfigApplicationContext,代码如下:

ApplicationContext context = new AnnotationConfigApplicationContext("com.pseudocode");

参数中传入的是一个包路径,Spring 会在这个包路径下扫描包含指定注解的类型,并依此来初始化上下文对象。这里调用的构造方法,也会在之后,作为源码分析的入口。在分析之前,我们还需要大概了解一下 AnnotationConfigApplicationContext 这个类。

上图中是 AnnotationConfigApplicationContext 继承的父类和实现的接口,可以看到它和 ClassPathXmlApplicationContext 一样,都是 AbstractApplicationContext 的子类,因此,AnnotationConfigApplicationContext 初始化上下文过程中,涉及到 AbstractApplicationContext 类中实现的部分,都是和 ClassPathXmlApplicationContext 一样的。

下面,我们从构造方法入手,分析 Spring 基于注解初始化上下文的原理。

上下文初始化

首先找到方法体的代码。

public AnnotationConfigApplicationContext(String... basePackages) {
   this();
   scan(basePackages);
   refresh();
}

从方法的参数类型可以看出,这里其实可以传入多个包路径。在方法体中,有很清晰的三个步骤:

  • 首先,调用了当前类的无参构造方法
  • 然后,以basePackages作为参数,调用了scan方法,从方法名称可以看出,这个方法的作用,是在我们提供的包路径下,扫描添加了特定注解的组件。
  • 最后,调用了refresh方法。

如果之前已经了解过了 ClassPathXmlApplicationContext 的初始化过程,这里的refresh方法应该非常熟悉,它是 Spring 上下文初始化的核心逻辑。这个方法的实现逻辑被定义在 AbstractApplicationContext 抽象类中,它是 ClassPathXmlApplicationContext 和 AnnotationConfigApplicationContext 共同的父类,因此,这里调用的refresh方法就是我在之前的文章中已经分析过的那个refresh方法,这里就不再对它进行分析了,具体的内容可以阅读【Spring Framework 源码解读】专栏的第 2 到 18 篇源码分析文章,有详细的介绍。如果想要快速了解,也可以参考这篇总结文章

因此,这里我们只对构造方法中的前两行代码做分析。

无参构造方法

以下是 AnnotationConfigApplicationContext 的无参构造方法的代码。

public AnnotationConfigApplicationContext() {
   this.reader = new AnnotatedBeanDefinitionReader(this);
   this.scanner = new ClassPathBeanDefinitionScanner(this);
}

在 Java 中,无参构造方法执行的时候,会先执行其父类的无参构造方法,因此,我们需要沿着继承关系,寻找更多的无参构造方法中会被执行的逻辑。

下面分别介绍一下这些类的午餐构造方法中都做了什么,就不一一贴代码了:

  • 在 DefaultResourceLoader 的无参构造方法中,初始化了当前的上下文的类加载器,对应的成员变量是classLoader
  • 在 AbstractApplicationContext 的无参构造方法中,初始化了资源模式解析器resourcePatternResolver。在创建资源模式解析器的时候,会用到上一步初始化好的类加载器。
  • 在 GenericApplicationContext 的无参构造方法中,初始化了当前上下文内置的 BeanFactory 容器,类型为 DefaultListableBeanFactory,与 ClassPathXmlApplicationContext 中内置的 BeanFactory 类型相同。在创建 BeanFactory 时,还会在上下文的ignoredDependencyInterfaces集合中,添加 BeanNameAware、BeanFactoryAware、BeanClassLoaderAware 三个感知接口类型。

介绍完父类的无参构造方法之后,下面看 AnnotationConfigApplicationContext 的无参构造方法中的两行代码,其实就是初始化了readerscanner两个成员变量,他们分别是 AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 类型,可以看出他们都是用来处理和 BeanDefinition 有关的工作,创建时也都将当前上下文作为构造方法中registry参数的值传入。

下面分别看一下两者初始化的过程。

AnnotatedBeanDefinitionReader 初始化

直接进入构造方法:

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
   this(registry, getOrCreateEnvironment(registry));
}

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
   Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
   Assert.notNull(environment, "Environment must not be null");
   this.registry = registry;
   this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
   AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

其中,比较关键的就是最后一行对AnnotationConfigUtils.registerAnnotationConfigProcessors方法的调用,进入这个方法查看代码。

虽然方法中的代码量不少,但是很容易看到其中有很多相似的逻辑,其实整个方法就是在向容器中注册了一些特定的 BeanDefinition,其中大部分都是跟注解相关的后处理器。

ClassPathBeanDefinitionScanner 初始化

下面看 ClassPathBeanDefinitionScanner 的初始化过程。

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
   this(registry, true);
}

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
   this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
}

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
      Environment environment) {
   this(registry, useDefaultFilters, environment,
         (registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
}

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
      Environment environment, @Nullable ResourceLoader resourceLoader) {
   Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
   this.registry = registry;
   if (useDefaultFilters) {
      registerDefaultFilters();
   }
   setEnvironment(environment);
   setResourceLoader(resourceLoader);
}

这里经过了四次的构造方法调用,在过程中可以知道,最下面的一个方法被调用时,useDefaultFilters参数传入的值时true。其中比较关键的代码也是registerDefaultFilters方法的调用。

进入registerDefaultFilters方法。

protected void registerDefaultFilters() {
   this.includeFilters.add(new AnnotationTypeFilter(Component.class));
   ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
   try {
      this.includeFilters.add(new AnnotationTypeFilter(
            ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
      logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
   }
   catch (ClassNotFoundException ex) {
      // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
   }
   try {
      this.includeFilters.add(new AnnotationTypeFilter(
            ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
      logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
   }
   catch (ClassNotFoundException ex) {
      // JSR-330 API not available - simply skip.
   }
}

这个方法的主要逻辑,就是向includeFilters成员变量中,添加了几个 AnnotationTypeFilter 类型的对象,AnnotationTypeFilter 是一个注解类型的过滤器,主要是在后续的组件扫描时用来过滤添加了特定的注解的类。

这里的代码逻辑中也可以看到,除了 ManagedBean 和 Named 之外,还添加了 Spring 中的 Component 注解,这个注解定义在org.springframework.stereotype包中。这里的 AnnotationTypeFilter 的作用,在后续流程中再介绍。

后续流程

至此,AnnotationConfigApplicationContext 构造器中的逻辑就分析完了,之后就是在包路径下扫描组件的逻辑,也就是下面这行代码。

scan(basePackages);

进入scan方法。

@Override
public void scan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   this.scanner.scan(basePackages);
}

这里调用了scannerscan方法,这个scanner就是上一步骤中初始化的 ClassPathBeanDefinitionScanner 类型的成员变量。再进入方法查看源码。

public int scan(String... basePackages) {
   int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
   doScan(basePackages);
   // Register annotation config processors, if necessary.
   if (this.includeAnnotationConfig) {
      AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
   }
   return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

这里还有很多要介绍的内容,我会在下一篇文章中单独分析scan方法中的代码。

总结

本文分析了基于注解初始化 Spring 上下文的部分原理,包括与基于 XML 初始化上下文的对比,在进行组件的扫描之前,Spring 会向容器中注册一些注解相关的后处理器,并添加了默认的注解类型过滤器。下一篇讲重点分析基于注解的组件扫描的原理。


相关阅读: