AnnotationConfigApplicationContext概述
此前我们学的IoC容器初始化源码系列文章:Spring 5.x 源码,都是基于ClassPathXmlApplicationContext上下文容器来讲解的,我们在第一篇文章的开始就说过Spring提供的上下文容器有很多种,ClassPathXmlApplicationContext上下文默认从相对路径加载Spring XML配置文件并初始化容器的,类似的还有FileSystemXmlApplicationContext,它默认是从绝对路径加载XML文件的,虽然它们都支持注解的配置,但是其特点就是不能脱离XML配置文件,都是基于XML启动的。
到如今,XML配置文件使用的越来越少了,基本上都是properties属性文件和纯注解搭配Java配置的开发方式,也就是Spring Boot。实际上,Spring Framework就提供了一个采用纯注解开发和Java配置启动的非web环境下的上下文容器,那就是AnnotationConfigApplicationContext,我们先看看AnnotationConfigApplicationContext和其他容器的联系与区别,一些重要类和接口的uml类图如下:
可以看到,AnnotationConfigApplicationContext继承了GenericApplicationContext类,而GenericApplicationContext继承了AbstractApplicationContext,这就和ClassPathXmlApplicationContext与FileSystemXmlApplicationContext来自于同一个父类,因此它们具有相同的核心功能。
AnnotationConfigApplicationContext还是实现了BeanDefinitionRegistry接口,可用于注册bean定义,实际上是注册到GenericApplicationContext内部的DefaultListableBeanFactory对象属性中的。
AnnotationConfigApplicationContext还实现了AnnotationConfigRegistry接口,因此具有支持通过Java配置类(register方法)和直接扫描包路径(scan方法)来启动容器的能力。
/**
* 用于注解配置应用上下文的公共接口,定义了register和scan方法
*
* @since 4.1
*/
public interface AnnotationConfigRegistry {
/**
* 注册一个或多个要处理的组件类
*/
void register(Class<?>... componentClasses);
/**
* 在指定的包包内执行扫描,并注册组件类
*/
void scan(String... basePackages);
}
AnnotationConfigApplicationContext没有实现Refreshable体系,因此不支持通过指定XML配置文件位置的启动。
就像ClassPathXmlApplicationContext与FileSystemXmlApplicationContext仅支持非web环境一样,AnnotationConfigApplicationContext也是仅仅用于非web环境,web环境通常使用XmlWebApplicationContext或者AnnotationConfigWebApplicationContext或者AnnotationConfigServletWebServerApplicationContext等web专用上下文,这些上下文等我们后面学习了Spring MVC时再讲解。
AnnotationConfigApplicationContext最主要的特征就是支持从给定的包路径和配置类启动容器,这就是对AnnotationConfigRegistry接口的支持:
AnnotationConfigApplicationContext(Class<?>… componentClasses)构造器
这个构造器传递组件类数组,根据组件类创建容器。相比于ClassPathXmlApplicationContext,少了setConfigLocations方法,多了register方法,但是最终它们都是调用的同一个refresh核心方法!
首先调用无参构造器,初始化一个空容器和一系列属性;
随后调用register方法注册参数组件类;
最后调用refresh方法刷新容器,这个方法我们在前面的IoC容器初始化文章中花了大量篇幅讲解。实际上,内部调用到的很多方法我们在IoC容器初始化的部分都讲过了!我们只会讲是讲前两步。
/**
* 创建新的AnnotationConfigApplicationContext,从给定的组件类派生 bean 定义并自动刷新上下文
*
* @param componentClasses 一个或多个组件类,例如@Configuration类
*/
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
//调用无参构造器,初始化reader和scanner
this();
//自动调用方法注册组件类
register(componentClasses);
//自动调用refresh方法刷新容器,这个方法我们在前面的IoC容器初始化文章中花了大量篇幅讲解
refresh();
}
this()无参构造器
调用无参构造器,主要就是初始化一个AnnotatedBeanDefinitionReader和一个ClassPathBeanDefinitionScanner,它们的内部又会各自初始化一系列属性,为后续加载bean定义和各种配置做准备。
//-------------AnnotationConfigApplicationContext的两个属性---------------
/**
* 注解Bean定义读取器
*/
private final AnnotatedBeanDefinitionReader reader;
/**
* 类路径Bean定义扫描器
*/
private final ClassPathBeanDefinitionScanner scanner;
/**
* AnnotationConfigApplicationContext的无参构造器
* <p>
* 创建新的AnnotationConfigApplicationContext,需要通过register方法注册组件,然后手动调用refresh方法刷新
*/
public AnnotationConfigApplicationContext() {
//设置注解Bean定义读取器
this.reader = new AnnotatedBeanDefinitionReader(this);
//设置类路径Bean定义扫描器
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
同时还会默认调用父类的无参构造器,比如GenericApplicationContext,它同样会初始化一个内部bean工厂:
/**
* GenericApplicationContext的属性
* <p>
* AnnotationConfigApplicationContext内部的bean工厂,保存在父类中
*/
private final DefaultListableBeanFactory beanFactory;
/**
1. 父类GenericApplicationContext的无参构造器
*/
public GenericApplicationContext() {
//初始化bean工厂
this.beanFactory = new DefaultListableBeanFactory();
}
new AnnotatedBeanDefinitionReader
在AnnotationConfigApplicationContext的空构造器中,将会创建一个注解Bean定义读取器AnnotatedBeanDefinitionReader,用于显示的先容器注册bean class,外层的register方法就是调用AnnotatedBeanDefinitionReader的方法。
在初始化AnnotationConfigApplicationContext过程中:
会为容器和自己创建新的StandardEnvironment标准环境变量,并会默认初始化systemProperties - JVM系统属性属性源和systemEnvironment - 系统环境属性源。具体逻辑我们在前面的"IoC容器初始化(1)"的文章中就详细说了。
会创建一个ConditionEvaluator条件评估器,用于解析@Conditional注解。我们在前面讲ConfigurationClassPostProcessor的文章中就见过了。
最重要的是,会调用AnnotationConfigUtils.registerAnnotationConfigProcessors方法向容器注册一系列的注解配置后处理器,用于后续扫描包、创建bean实例等过程中对大量相关注解的处理,比如ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor等重要的后处理器都是在这里注册的。在解析< context:component-scan/>、< context:annotation-config/>标签的时候也会调用方法,我们在前面的"IoC容器初始化(3)"的文章中就详细说了。
/**
* AnnotatedBeanDefinitionReader的构造器
* <p>
* 使用给定的注册表创建一个AnnotatedBeanDefinitionReader
* <p>
* 如果注册表是EnvironmentCapable类型,例如ApplicationContext
* 则Environment环境变量将被继承,否则将创建和使用新的StandardEnvironment标准环境变量
*
* @param registry 用于加载注册bean定义的BeanFactory,就是当前AnnotationConfigApplicationContext对象
*/
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
//调用另一个构造器
this(registry, getOrCreateEnvironment(registry));
}
/**
* AnnotatedBeanDefinitionReader的方法
* <p>
* 从给定的注册表获取环境变量,如果没有就返回新的StandardEnvironment标准环境变量对象
* 将会默认初始化systemProperties - JVM 系统属性属性源和systemEnvironment - 系统环境属性源
*/
private static Environment getOrCreateEnvironment(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
//如果属于EnvironmentCapable类型,即可获取环境,AnnotationConfigApplicationContext就属于该类型
if (registry instanceof EnvironmentCapable) {
/*
* 调用getEnvironment获取环境变量,这个方法实际上就是父类AbstractApplicationContext的方法
* 如果AbstractApplicationContext的environment属性为null,就会创建一个StandardEnvironment对象返回
* 这个StandardEnvironment环境变量我们在IoC容器初始化的第一篇文章就讲过了默认会初始化:
* systemProperties - JVM 系统属性属性源和systemEnvironment - 系统环境属性源,这是两个基本属性源
*/
return ((EnvironmentCapable) registry).getEnvironment();
}
return new StandardEnvironment();
}
//----------AnnotatedBeanDefinitionReader的相关属性---------
/**
* bean定义注册表
*/
private final BeanDefinitionRegistry registry;
/**
* 条件评估器
*/
private ConditionEvaluator conditionEvaluator;
/**
* AnnotatedBeanDefinitionReader的构造器
* <p>
* 使用给定的注册表和环境变量创建新的AnnotatedBeanDefinitionReader
*
* @param registry 注册bean定义的注册表
* @param environment 评估 bean 定义配置文件时要使用的环境变量
*/
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(environment, "Environment must not be null");
//为registry属性赋值,就是当前AnnotationConfigApplicationContext实例
this.registry = registry;
//为conditionEvaluator属性赋值,新建一个ConditionEvaluator
this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
/*
* 注册一系列的注解配置后处理器,用于后续创建bean实例过程中对大量相关注解的处理,比如ConfigurationClassPostProcessor
* AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor等重要的后处理器都是在这里注册的
*
* 在解析< context:component-scan/>、< context:annotation-config/>标签的时候也会调用方法
* 这个方法我们在前面的"IoC容器初始化(3)"的文章中就详细说过了
*/
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
new ClassPathBeanDefinitionScanner
在AnnotationConfigApplicationContext的空构造器中,将会创建一个配置扫描器ClassPathBeanDefinitionScanner,主要目的调用它的doScan方法在指定的basePackage包路径中扫描符合规则的bean的定义,并且注册到注册表缓存中,这实际上就是类似于“IoC容器初始化(3)”文章中的对于< context:component-scan/>扩展标签解析的部分。
这个构造器最终会调用四个参数的构造器,主要功能是:
注册默认的类型过滤器,尝试添加@Component、@ManagedBean、@Named这三个注解类型过滤器到includeFilters缓存集合中!
加载"META-INF/spring.components"组件索引文件,避免扫描包,提升应用启动速度,这是Spring 5的新特性。
/**
* ClassPathBeanDefinitionScanner的构造器
*
* @param registry bean定义注册表
*/
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
this(registry, true);
}
/**
* ClassPathBeanDefinitionScanner的构造器
*
* @param registry bean定义注册表
* @param useDefaultFilters 是否使用DefaultFilters,默认传递true
*/
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
}
/**
* ClassPathBeanDefinitionScanner的构造器
*/
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment) {
/*
* 最终调用4个参数的构造器,主要功能是:
* 1 注册默认的类型过滤器,尝试添加@Component、@ManagedBean、@Named这三个注解类型过滤器到includeFilters缓存集合中!
* 2 加载"META-INF/spring.components"组件索引文件,避免扫描包,提升应用启动速度
* 该构造器及其上两个知识点,我们在"IoC容器初始化(3)"文章的createScanner方法中就讲过了,
*/
this(registry, useDefaultFilters, environment,
(registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
}
register注册组件类
构造器的第二步,就是调用register方法注册要处理的一个或多个参数组件类。其内部还是调用reader的同名方法委托AnnotatedBeanDefinitionReader进行解析的。如果我们已经知道了基于XML的IoC容器初始化流程
可以看到传递参数组件类将被注册成为AnnotatedGenericBeanDefinition类型的bean定义,同时还会解析该类上的比如@Conditional、@Scope、@Lazy、 @Primary、@DependsOn、@Role、@Description……等当前bean定义相关的注解。
因此,就算参数组件类是一个什么注解和功能没有的普通类,同样会被注册到容器中,但是通常,我们会传递一个@Configuration注解的配置类,并且还包含@ComponentScan、@PropertySource等配置注解。因此,在register方法之后的refresh方法中就能根据这一个注册的配置类来构建满足条件的容器了。
/**
* AnnotationConfigApplicationContext实现的AnnotationConfigRegistry接口的方法
* <p>
* 注册要处理的一个或多个组件类。请注意,必须调用refresh方法才能使上下文完全处理新的类。
*
* @param componentClasses 一个或多个组件类,例如,@Configuration 类
*/
@Override
public void register(Class<?>... componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
//通过AnnotatedBeanDefinitionReader注册组件类
this.reader.register(componentClasses);
}
/**
* AnnotatedBeanDefinitionReader的方法
* <p>
* 注册要处理的一个或多个组件类。注册是幂等的,多次添加同一组件类没有其他效果。
*
* @param componentClasses 一个或多个组件类,例如,@Configuration 类
*/
public void register(Class<?>... componentClasses) {
for (Class<?> componentClass : componentClasses) {
//调用registerBean方法处理每一个组件类
registerBean(componentClass);
}
}
/**
* AnnotatedBeanDefinitionReader的方法
* <p>
* 根据给定的 bean class注册 bean 定义,从类声明的注解派生其元数据。
*
* @param beanClass bean class
*/
public void registerBean(Class<?> beanClass) {
//调用doRegisterBean方法,这才是真正的注册方法
doRegisterBean(beanClass, null, null, null, null);
}
//---------AnnotatedBeanDefinitionReader的相关属性-------------
/**
* 条件评估器,在创建AnnotatedBeanDefinitionReader实例时初始化,用于解析@Conditional注解判断是否跳过某项处理
*/
private ConditionEvaluator conditionEvaluator;
/**
* 用于解析 bean 定义scope作用域的策略接口,默认是AnnotationScopeMetadataResolver
*/
private ScopeMetadataResolver scopeMetadataResolver = new AnnotationScopeMetadataResolver();
/**
* beanName生成器,默认是AnnotationBeanNameGenerator
*/
private BeanNameGenerator beanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;
/**
* AnnotatedBeanDefinitionReader的方法
* <p>
* 从给定的 bean class类注册 bean定义,从类声明的注解派生其元数据。
*
* @param beanClass bean class
* @param name bean 的显式名称,传递null
* @param qualifiers 除了 bean 类级别的限定符之外,还要考虑特定的限定符注解(如果有),传递null
* @param supplier 用于创建 bean 实例的回调,可能是null,传递null
* @param customizers 用于自定义工厂的 Bean 定义(例如设置lazy-init或primary标志)的一个或多个回调,传递null
*/
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
//创建AnnotatedGenericBeanDefinition类型的bean定义
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
/*
* 如果需要跳过当前组件类的处理,那么该方法直接返回
* 这个方法,我们同样在解析ConfigurationClassPostProcessor后处理器的文章中就详细讲解过了
* 通过判断class上的@Conditional条件注解是否满足Condition条件来控制是否跳过该配置类的解析。
*/
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
/*
* 用于创建bean实例的回调生产者,Spring 5.0和Java8的新特性,默认为null
* 如果通过supplier创建了bean实例,那么将不会走Spring的创建的逻辑,这一步我们以前也讲过了
*/
abd.setInstanceSupplier(supplier);
//解析@Scope注解,设置scope作用域
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
/*
* 对于组件类的解析,由于name传递null,因此将使用beanNameGenerator生成beanName,类型是AnnotationBeanNameGenerator
* 生成规则是默认使用小写开头的简单类名(要求开头没有连续超过两个大写字符)作为beanName
* 具体规则源码,我们在"IoC容器初始化(3)"文章中已经详细讲过了
*/
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
/*
* 处理类上的其他通用注解:@Lazy, @Primary, @DependsOn, @Role, @Description
* 具体的源码,我们在"IoC容器初始化(3)"文章中已经详细讲过了
*/
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
//对于组件类的解析,qualifiers为null
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
} else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
} else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
//对于组件类的解析,customizers为null
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
//根据bean定义和beanName生成BeanDefinitionHolder对象
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
//设置代理的方式ScopedProxyMode
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
/*
* 核心方法。调用BeanDefinitionReaderUtils.registerBeanDefinition方法将解析之后的BeanDefinition注册到Registry中,
* 这个Registry实际上就是当前上下文容器AnnotationConfigApplicationContext。
* 具体的源码,我们在"IoC容器初始化(3)"文章中已经详细讲过了
*/
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
AnnotationConfigApplicationContext(String… basePackages)构造器
这个构造器传递包路径数组,根据包路径创建容器。相比于ClassPathXmlApplicationContext,少了setConfigLocations方法,多了scan方法,但是最终它们都是调用的同一个refresh核心方法!
/**
* 创建新的注释配置应用程序上下文,扫描给定包中的组件,为这些组件注册 bean 定义,并自动刷新上下文。
*
* @param basePackages 要扫描组件类的包
*/
public AnnotationConfigApplicationContext(String... basePackages) {
//调用无参构造器,初始化reader和scanner
this();
//扫描给定包中的组件,为这些组件注册 bean 定义
scan(basePackages);
//自动调用refresh方法刷新容器,这个方法我们在前面的IoC容器初始化文章中花了大量篇幅讲解
refresh();
}
我们看看scan方法就行了!
scan扫描包
调用scan方法扫描给定包中的组件,为这些组件注册 bean 定义。其内部还是调用scanner的同名方法以及doScan方法委托ClassPathBeanDefinitionScanner进行解析的。
/**
* AnnotationConfigApplicationContext的方法
* <p>
* 扫描给定包中的组件,为这些组件注册 bean 定义
*
* @param basePackages 要扫描组件类的包
*/
@Override
public void scan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
//通过ClassPathBeanDefinitionScanner扫描包
this.scanner.scan(basePackages);
}
/**
* ClassPathBeanDefinitionScanner的属性
* 是否包括注解配置的解析,默认true
*/
private boolean includeAnnotationConfig = true;
/**
* ClassPathBeanDefinitionScanner的方法
* <p>
* 扫描给定包中的组件,为这些组件注册 bean 定义
*
* @param basePackages 要扫描组件类的包
* @return 本次扫描注册的bean定义数量
*/
public int scan(String... basePackages) {
//扫描之前容器中的bean定义数量
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
/*
* 核心方法,执行扫描,该方法在之前“IoC容器初始化(3)”的文章中就讲过了。
* */
doScan(basePackages);
//如有必要,注册注解配置处理器
if (this.includeAnnotationConfig) {
/*
* 注册一系列的注解配置后处理器,用于后续创建bean实例过程中对大量相关注解的处理,比如ConfigurationClassPostProcessor
* AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor等重要的后处理器都是在这里注册的
*
* 在解析< context:component-scan/>、< context:annotation-config/>标签的时候也会调用方法
* 这个方法我们在前面的"IoC容器初始化(3)"的文章中就详细说过了
*/
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
//返回本次扫描注册的bean定义数量
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
AnnotationConfigApplicationContext总结
本文的长度比较短,是否说明AnnotationConfigApplicationContext IoC容器初始化的原理很“简单”呢?非也,本文的默认是在认为大家已经看完了之前的IoC容器初始化系列以及PropertySourcesPlaceholderConfigurer、ConfigurationClassPostProcessor等重要后处理器的全部二十几万字的文章的基础上进行解析的,省略了几乎全部的关键的知识点,比如refresh核心方法,因为在以前已经非常详细的讲过了。
AnnotationConfigApplicationContext的初始化,this()主要就是初始化一系列属性加载一系列默认配置,为后续的refersh方法做准备,主要包括:
this()中创建的AnnotatedBeanDefinitionReader:
reader本身用于手动注册参数组件类成为AnnotatedGenericBeanDefinition类型的bean定义到容器中,后续refersh方法就是根据这些bean定义做出进一步扩展。
初始化默认StandardEnvironment标准环境变量,并会默认初始化systemProperties - JVM 系统属性属性源和systemEnvironment - 系统环境属性源。
创建ConditionEvaluator条件评估器件,通过评估参数组件类上的@Conditional是否满足条件来决定是否解析、注册参数组件类。
最重要的是,会调用AnnotationConfigUtils.registerAnnotationConfigProcessors方法向容器注册一系列的注解配置后处理器,用于后续扫描包、创建bean实例等过程中对大量相关注解的处理,比如ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor等重要的后处理器都是在这里注册的。
this()中创建的ClassPathBeanDefinitionScanner:
scanner本身用于调用它的scan和doScan方法在指定的basePackage包路径中扫描符合规则的bean的定义,并且注册到注册表中,主要是对于传递包路径的构造器的支持。
scanner的scan方法同样会调用AnnotationConfigUtils.registerAnnotationConfigProcessors方法向容器注册一系列的注解配置后处理器。
第二步register或者scan方法就是先解析参数参数配置类或者包路径成为bean定义并注册到注册表中,最后一步的refresh方法就是使用在第一步初始化的各种属性配置和后处理器进一步解析在第二步注册的bean定义,进而初始化整个容器。