Spring 源码阅读 31:基于注解初始化 Spring 上下文的原理(1)
基于 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
方法。
因此,这里我们只对构造方法中的前两行代码做分析。
无参构造方法
以下是 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 的无参构造方法中的两行代码,其实就是初始化了reader
和scanner
两个成员变量,他们分别是 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);
}
这里调用了scanner
的scan
方法,这个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 会向容器中注册一些注解相关的后处理器,并添加了默认的注解类型过滤器。下一篇讲重点分析基于注解的组件扫描的原理。
相关阅读:
转载自:https://juejin.cn/post/7148728014287847437