该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读

Spring Boot 版本:2.2.x

最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章导读》 系列文章

如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~

该系列其他文章请查看:《精尽 Spring Boot 源码分析 - 文章导读》

概述

现如今,Spring Boot 在许多中大型企业中被普及,想必大家对于 @SpringBootApplication 并不陌生,这个注解通常标注在我们应用的启动类上面,标记是一个 Spring Boot 应用,同时开启自动配置的功能,那么你是否有深入了解过该注解呢?没有的话,或许这篇文章可以让你对它有一个新的认识。

提示:@EnableAutoConfiguration 是开启自动配置功能的模块驱动注解,是 Spring Boot 的核心注解

整篇文章主要是对这个注解,也就是 Spring Boot 的自动配置功能进行展述

@SpringBootApplication

org.springframework.boot.autoconfigure.SpringBootApplication 注解在 Spring Boot 的 spring-boot-autoconfigre 子模块下,当我们引入 spring-boot-starter 模块后会自动引入该子模块

该注解是一个组合注解,如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited // 表明该注解定义在某个类上时,其子类会继承该注解
@SpringBootConfiguration // 继承 `@Configuration` 注解
@EnableAutoConfiguration // 开启自动配置功能
// 扫描指定路径下的 Bean
@ComponentScan( excludeFilters = {
// 默认没有 TypeExcludeFilter
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
// 排除掉自动配置类
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
/**
* 需要自动配置的 Class 类
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {}; /**
* 需要自动配置的类名称
*/
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {}; /**
* 需要扫描的路径
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {}; /**
* 需要扫描的 Class 类
*/
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {}; /**
* 被标记的 Bean 是否进行 CGLIB 提升
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}

@SpringBootApplication 注解就是一个组合注解,里面的每个配置都是元注解中对应的属性,上面已做描述

该注解上面的 @Inherited 元注解是 Java 提供的,标注后表示当前注解定义在某个类上时,其子类会继承该注解,我们一起来看看其他三个注解

@SpringBootConfiguration

org.springframework.boot.SpringBootConfiguration 注解,Spring Boot 自定义注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration { /**
* 被标记的 Bean 是否进行 CGLIB 提升
*/
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}

该注解很简单,上面标注了 @Configuration 元注解,所以作用相同,同样是将一个类标注为配置类,能够作为一个 Bean 被 Spring IoC 容器管理

至于为什么不直接使用 @Configuration 注解呢,我想这应该是 领域驱动设计 中的一种思想,可以使得 Spring Boot 更加灵活,总有它的用武之地

领域驱动设计:Domain-Driven Design,简称 DDD。过去系统分析和系统设计都是分离的,这样割裂的结果导致需求分析的结果无法直接进行设计编程,而能够进行编程运行的代码却扭曲需求,导致客户运行软件后才发现很多功能不是自己想要的,而且软件不能快速跟随需求变化。DDD 则打破了这种隔阂,提出了领域模型概念,统一了分析和设计编程,使得软件能够更灵活快速跟随需求变化。

@ComponentScan

org.springframework.context.annotation.ComponentScan 注解,Spring 注解,扫描指定路径下的标有 @Component 注解的类,解析成 Bean 被 Spring IoC 容器管理

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan { /**
* 指定的扫描的路径
*/
@AliasFor("basePackages")
String[] value() default {}; /**
* 指定的扫描的路径
*/
@AliasFor("value")
String[] basePackages() default {}; /**
* 指定的扫描的 Class 对象
*/
Class<?>[] basePackageClasses() default {}; Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class; Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class; ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT; String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN; boolean useDefaultFilters() default true; /**
* 扫描时的包含过滤器
*/
Filter[] includeFilters() default {};
/**
* 扫描时的排除过滤器
*/
Filter[] excludeFilters() default {}; boolean lazyInit() default false;
}

想深入了解该注解的小伙伴可以查看我前面对 Spring IoC 进行源码分析的文章:

该注解通常需要和 @Configuration 注解一起使用,因为需要先被当做一个配置类,然后解析到上面有 @ComponentScan 注解后则处理该注解,通过 ClassPathBeanDefinitionScanner 扫描器去扫描指定路径下标注了 @Component 注解的类,将他们解析成 BeanDefinition(Bean 的前身),后续则会生成对应的 Bean 被 Spring IoC 容器管理

当然,如果该注解没有通过 basePackages 指定路径,Spring 会选在以该注解标注的类所在的包作为基础路径,然后扫描包下面的这些类

@EnableAutoConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration 注解,Spring Boot 自定义注解,用于驱动 Spring Boot 自动配置模块

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 注册一个 Bean 保存当前注解标注的类所在包路径
@Import(AutoConfigurationImportSelector.class) // Spring Boot 自动配置的实现
public @interface EnableAutoConfiguration {
/**
* 可通过这个配置关闭 Spring Boot 的自动配置功能
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; /**
* 指定需要排除的自动配置类的 Class 对象
*/
Class<?>[] exclude() default {}; /**
* 指定需要排除的自动配置类的名称
*/
String[] excludeName() default {};
}

对于 Spring 中的模块驱动注解的实现都是通过 @Import 注解来实现的

模块驱动注解通常需要结合 @Configuration 注解一起使用,因为需要先被当做一个配置类,然后解析到上面有 @Import 注解后则进行处理,对于 @Import 注解的值有三种情况:

  1. 该 Class 对象实现了 ImportSelector 接口,调用它的 selectImports(..) 方法获取需要被处理的 Class 对象的名称,也就是可以将它们作为一个 Bean 被 Spring IoC 管理

    • 该 Class 对象实现了 DeferredImportSelector 接口,和上者的执行时机不同,在所有配置类处理完后再执行,且支持 @Order 排序
  2. 该 Class 对象实现了 ImportBeanDefinitionRegistrar 接口,会调用它的 registerBeanDefinitions(..) 方法,自定义地往 BeanDefinitionRegistry 注册中心注册 BeanDefinition(Bean 的前身)

  3. 该 Class 对象是一个 @Configuration 配置类,会将这个类作为一个 Bean 被 Spring IoC 管理

对于 @Import 注解不熟悉的小伙伴可查看我前面的 《死磕Spring之IoC篇 - @Bean 等注解的实现原理》 这篇文章

这里的 @EnableAutoConfiguration 自动配置模块驱动注解,通过 @Import 导入 AutoConfigurationImportSelector 这个类(实现了 DeferredImportSelector 接口)来驱动 Spring Boot 的自动配置模块,下面会进行分析

@AutoConfigurationPackage

我们注意到 @EnableAutoConfiguration 注解上面还有一个 @AutoConfigurationPackage 元注解,它的作用就是注册一个 Bean,保存了当前注解标注的类所在包路径

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
/**
* 将当前注解所标注的类所在包名封装成一个 {@link org.springframework.boot.autoconfigure.AutoConfigurationPackages.BasePackages} 进行注册
* 例如 JPA 模块的会使用到这个对象(JPA entity scanner)
*/
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage { }

同样这里使用了 @Import 注解来实现的,对应的是一个 AutoConfigurationPackages.Registrar 内部类,如下:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

    @Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 注册一个 BasePackages 类的 BeanDefinition,角色为内部角色,名称为 `org.springframework.boot.autoconfigure.AutoConfigurationPackages`
register(registry, new PackageImport(metadata).getPackageName());
} @Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
// 将注解元信息封装成 PackageImport 对象,对注解所在的包名进行封装
return Collections.singleton(new PackageImport(metadata));
}
}

比较简单,这里直接跳过了

自动配置

在开始之前,我们先来了解一下 Spring Boot 的自动配置,就是通过引入某个功能的相关 jar 包依赖后,Spring Boot 能够自动配置应用程序,让我们很方便的使用该功能

  • 例如当你引入 spring-boot-starter-aop 后,会自动引入 AOP 相关的 jar 包依赖,那么在 spring-boot-autoconfigure 中有一个 AopAutoConfiguration 自动配置类会自动驱动整个 AOP 模块

  • 例如当你引入 spring-boot-starter-web 后,会自动引入 Spring MVC、Tomcat 相关的 jar 包依赖,那么在 spring-boot-autoconfigure 中会有相应的自动配置类会自动配置 Spring MVC

当然,还有许多自动配置类,结合这 Spring Boot 的 Starter 模块,让许多功能或者第三方 jar 包能够很简便的和 Spring Boot 整合在一起使用

现在很多开源框架都提供了对应的 Spring Boot Starter 模块,能够更好的整合 Spring Boot,当你熟悉自动配置功能后,你也可以很轻松的写一个 Starter 包供他人使用

这里先提前剧透一下,自动配置类为什么在你引入相关 jar 包后会自动配置对应的模块呢?

主要就是拓展了 Spring 的 Condition,例如 @ConditionalOnClass 注解,当存在指定的 Class 对象时才注入某个 Bean

同时也可以再结合 @EnableXxx 模块注解,通过 @Import 注解驱动某个模块

具体细节,请继续往下看

AutoConfigurationImportSelector

org.springframework.boot.autoconfigure.AutoConfigurationImportSelector,实现了 DeferredImportSelector 接口,是 @EnableAutoConfiguration 注解驱动自动配置模块的核心类

直接看到实现的 ImportSelector 接口的方法

1. selectImports 方法

selectImports(AnnotationMetadata) 方法,返回需要注入的 Bean 的类名称

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// <1> 如果通过 `spring.boot.enableautoconfiguration` 配置关闭了自动配置功能
if (!isEnabled(annotationMetadata)) {
// 返回一个空数组
return NO_IMPORTS;
}
/**
* <2> 解析 `META-INF/spring-autoconfigure-metadata.properties` 文件,生成一个 AutoConfigurationMetadata 自动配置类元数据对象
*
* 说明:引入 `spring-boot-autoconfigure-processor` 工具模块依赖后,其中会通过 Java SPI 机制引入 {@link AutoConfigureAnnotationProcessor} 注解处理器在编译阶段进行相关处理
* 其中 `spring-boot-autoconfigure` 模块会引入该工具模块(不具有传递性),那么 Spring Boot 在编译 `spring-boot-autoconfigure` 这个 `jar` 包的时候,
* 在编译阶段会扫描到带有 `@ConditionalOnClass` 等注解的 `.class` 文件,也就是自动配置类,将自动配置类的信息保存至 `META-INF/spring-autoconfigure-metadata.properties` 文件中
* 例如保存类 `自动配置类类名.注解简称` => `注解中的值(逗号分隔)` 和 `自动配置类类名` => `空字符串`
*
* 当然,你自己写的 Spring Boot Starter 中的自动配置模块也可以引入这个 Spring Boot 提供的插件
*/
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
// <3> 从所有的 `META-INF/spring.factories` 文件中找到 `@EnableAutoConfiguration` 注解对应的类(需要自动配置的类)
// 会进行过滤处理,然后封装在一个对象中
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
// <4> 返回所有需要自动配置的类
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

过程如下:

  1. 如果通过 spring.boot.enableautoconfiguration 配置关闭了自动配置功能,那么直接返回一个空数组
  2. 解析 META-INF/spring-autoconfigure-metadata.properties 文件,生成一个 AutoConfigurationMetadata 自动配置类元数据对象
  3. 调用 getAutoConfigurationEntry(..) 方法, 从所有的 META-INF/spring.factories 文件中找到 @EnableAutoConfiguration 注解对应的类(需要自动配置的类),会进行过滤处理,然后封装在一个 AutoConfigurationEntry 对象中
  4. 返回所有需要自动配置的类

上面第 2 步调用的方法:

final class AutoConfigurationMetadataLoader {

	protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

	private AutoConfigurationMetadataLoader() {
} static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
} static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
// <1> 获取所有 `META-INF/spring-autoconfigure-metadata.properties` 文件 URL
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path)
: ClassLoader.getSystemResources(path);
Properties properties = new Properties();
// <2> 加载这些文件并将他们的属性添加到 Properties 中
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
// <3> 将这个 Properties 封装到 PropertiesAutoConfigurationMetadata 对象中并返回
return loadMetadata(properties);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex);
}
} static AutoConfigurationMetadata loadMetadata(Properties properties) {
return new PropertiesAutoConfigurationMetadata(properties);
}
}

这一步困惑了我很久,因为在 Spring Boot 工程中根本找不到 META-INF/spring-autoconfigure-metadata.properties 文件,而我们自己也没有配置过,但是在我们自己的 Spring Boot 应用依赖的 spring-boot-autoconfigure.jar 包里面又存在这个文件,如下:

那么这是为什么呢?经过我长时间的 Google,找到了答案

  1. 在引入 spring-boot-autoconfigure-processor 工具模块依赖后,其中会通过 Java SPI 机制引入 AutoConfigureAnnotationProcessor 注解处理器在编译阶段进行相关处理

  2. 其中 spring-boot-autoconfigure 模块会引入该工具模块(不具有传递性),那么 Spring Boot 在编译 spring-boot-autoconfigure 这个 jar 包的时候,在编译阶段会扫描到带有 @ConditionalOnClass 等注解的 .class 文件,也就是自动配置类,然后将自动配置类的一些信息保存至 META-INF/spring-autoconfigure-metadata.properties 文件中

  3. 文件中保存了 自动配置类类名.注解简称 --> 注解中的值(逗号分隔)自动配置类类名 --> 空字符串

  4. 当然,你自己写的 Spring Boot Starter 中的自动配置模块也可以引入这个 Spring Boot 提供的插件

得到的结论:

至于为什么这么做,是因为 Spring Boot 提供的自动配置类比较多,而我们不可能使用到很多自动配置功能,大部分都没必要,如果每次你启动应用的过程中,都需要一个一个去解析他们上面的 Conditional 注解,那么肯定会有不少的性能损耗

这里,Spring Boot 做了一个优化,通过自己提供的工具,在编译阶段将自动配置类的一些注解信息保存在一个 properties 文件中,这样一来,在你启动应用的过程中,就可以直接读取该文件中的信息,提前过滤掉一些自动配置类,相比于每次都去解析它们所有的注解,性能提升不少

2. getAutoConfigurationEntry 方法

getAutoConfigurationEntry(AutoConfigurationMetadata, AnnotationMetadata) 方法,返回符合条件的自动配置类,如下:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// <1> 如果通过 `spring.boot.enableautoconfiguration` 配置关闭了自动配置功能
if (!isEnabled(annotationMetadata)) {
// 则返回一个“空”的对象
return EMPTY_ENTRY;
}
// <2> 获取 `@EnableAutoConfiguration` 注解的配置信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// <3> 从所有的 `META-INF/spring.factories` 文件中找到 `@EnableAutoConfiguration` 注解对应的类(需要自动配置的类)
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// <4> 对所有的自动配置类进行去重
configurations = removeDuplicates(configurations);
// <5> 获取需要排除的自动配置类
// 可通过 `@EnableAutoConfiguration` 注解的 `exclude` 和 `excludeName` 配置
// 也可以通过 `spring.autoconfigure.exclude` 配置
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// <6> 处理 `exclusions` 中特殊的类名称,保证能够排除它
checkExcludedClasses(configurations, exclusions);
// <7> 从 `configurations` 中将 `exclusions` 需要排除的自动配置类移除
configurations.removeAll(exclusions);
/**
* <8> 从 `META-INF/spring.factories` 找到所有的 {@link AutoConfigurationImportFilter} 对 `configurations` 进行过滤处理
* 例如 Spring Boot 中配置了 {@link org.springframework.boot.autoconfigure.condition.OnClassCondition}
* 在这里提前过滤掉一些不满足条件的自动配置类,在 Spring 注入 Bean 的时候也会判断哦~
*/
configurations = filter(configurations, autoConfigurationMetadata);
/**
* <9> 从 `META-INF/spring.factories` 找到所有的 {@link AutoConfigurationImportListener} 事件监听器
* 触发每个监听器去处理 {@link AutoConfigurationImportEvent} 事件,该事件中包含了 `configurations` 和 `exclusions`
* Spring Boot 中配置了一个 {@link org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener}
* 目的就是将 `configurations` 和 `exclusions` 保存至 {@link AutoConfigurationImportEvent} 对象中,并注册到 IoC 容器中,名称为 `autoConfigurationReport`
* 这样一来我们可以注入这个 Bean 获取到自动配置类信息
*/
fireAutoConfigurationImportEvents(configurations, exclusions);
// <10> 将所有的自动配置类封装成一个 AutoConfigurationEntry 对象,并返回
return new AutoConfigurationEntry(configurations, exclusions);
}

过程如下:

  1. 如果通过 spring.boot.enableautoconfiguration 配置关闭了自动配置功能,则返回一个“空”的对象

  2. 获取 @EnableAutoConfiguration 注解的配置信息

  3. 从所有的 META-INF/spring.factories 文件中找到 @EnableAutoConfiguration 注解对应的类(需要自动配置的类),保存在 configurations 集合中

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 从所有的 `META-INF/spring.factories` 文件中找到 `@EnableAutoConfiguration` 注解对应的类(需要自动配置的类)
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    // 如果为空则抛出异常
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
    }

    这个 SpringFactoriesLoader 是由 Spring 提供的一个类

  4. 对所有的自动配置类进行去重

    protected final <T> List<T> removeDuplicates(List<T> list) {
    return new ArrayList<>(new LinkedHashSet<>(list));
    }
  5. 获取需要排除的自动配置类,可通过 @EnableAutoConfiguration 注解的 excludeexcludeName 配置,也可以通过 spring.autoconfigure.exclude 配置

  6. 处理 exclusions 中特殊的类名称,保证能够排除它

  7. configurations 中将 exclusions 需要排除的自动配置类移除

  8. 调用 filter(..) 方法, 目的就是过滤掉一些不符合 Condition 条件的自动配置类,和在 1. selectImports 方法 小节中讲到的性能优化有关哦

  9. META-INF/spring.factories 找到所有的 AutoConfigurationImportListener 事件监听器,触发每个监听器去处理 AutoConfigurationImportEvent 事件,该事件中包含了 configurationsexclusions

    Spring Boot 中配置了一个监听器,目的就是将 configurationsexclusions 保存至 AutoConfigurationImportEvent 对象中,并注册到 IoC 容器中,名称为 autoConfigurationReport,这样一来我们可以注入这个 Bean 获取到自动配置类信息

  10. 将所有的自动配置类封装成一个 AutoConfigurationEntry 对象,并返回

整个过程不复杂,关键在于上面的第 3 步和第 8 步,先从所有的 META-INF/spring.factories 文件中找到 @EnableAutoConfiguration 注解对应的类(需要自动配置的类),然后进行过滤

3. filter 方法

filter(List<String>, AutoConfigurationMetadata) 方法,过滤一些自动配置类

我们得先知道这两个入参:

  1. 所有的自动配置类名称
  2. META-INF/spring-autoconfigure-metadata.properties 文件保存的 Spring Boot 的自动配置类的注解元信息(Sprng Boot 编译时生成的)

这里的目的就是根据 2 里面的注解元信息,先过滤掉一些自动配置类

private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
// <1> 将自动配置类保存至 `candidates` 数组中
String[] candidates = StringUtils.toStringArray(configurations);
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
/*
* <2> 从 `META-INF/spring.factories` 找到所有的 AutoConfigurationImportFilter 对 `candidates` 进行过滤处理
* 有 OnClassCondition、OnBeanCondition、OnWebApplicationCondition
*/
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
// <2.1> Aware 回调
invokeAwareMethods(filter);
// <2.2> 对 `candidates` 进行匹配处理,获取所有的匹配结果
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
// <2.3> 遍历匹配结果,将不匹配的自动配置类至空
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
skip[i] = true;
candidates[i] = null;
skipped = true;
}
}
}
// <3> 如果没有不匹配的结果则全部返回
if (!skipped) {
return configurations;
}
// <4> 获取到所有匹配的自动配置类,并返回
List<String> result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
return new ArrayList<>(result);
}

过程如下:

  1. 将自动配置类保存至 candidates 数组中

  2. META-INF/spring.factories 找到所有的 AutoConfigurationImportFiltercandidates 进行过滤处理

    # Auto Configuration Import Filters
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
    org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
    org.springframework.boot.autoconfigure.condition.OnClassCondition,\
    org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
    1. Aware 回调
    2. candidates 进行匹配处理,获取所有的匹配结果,注意,这里传入了 AutoConfigurationMetadata 对象
    3. 遍历匹配结果,将不匹配的自动配置类至空
  3. 如果没有不匹配的结果则全部返回

  4. 获取到所有匹配的自动配置类,并返回

关键在于上面的第 2 步,通过 Spring Boot 自己扩展的几个自动配置类过滤器进行过滤,由于这部分内容和 Spring Boot 拓展 Condition 相关,放入下篇文章进行分析

下面我们一起来看看上面 1. selectImports 方法 小节中讲到的性能优化,META-INF/spring-autoconfigure-metadata.properties 文件是如何生成的,文件的内容又是什么

AutoConfigureAnnotationProcessor

org.springframework.boot.autoconfigureprocessor.AutoConfigureAnnotationProcessor,Spring Boot 的 spring-boot-autoconfigure-processor 工具模块中的自动配置类的注解处理器,在编译阶段扫描自动配置类的注解元信息,并将他们保存至一个 properties 文件中

@SupportedAnnotationTypes({ "org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
"org.springframework.boot.autoconfigure.condition.ConditionalOnBean",
"org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate",
"org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication",
"org.springframework.boot.autoconfigure.AutoConfigureBefore",
"org.springframework.boot.autoconfigure.AutoConfigureAfter",
"org.springframework.boot.autoconfigure.AutoConfigureOrder" })
public class AutoConfigureAnnotationProcessor extends AbstractProcessor {
/**
* 生成的文件
*/
protected static final String PROPERTIES_PATH = "META-INF/spring-autoconfigure-metadata.properties";
/**
* 保存指定注解的简称和注解全称之间的对应关系(不可修改)
*/
private final Map<String, String> annotations; private final Map<String, ValueExtractor> valueExtractors; private final Properties properties = new Properties(); public AutoConfigureAnnotationProcessor() {
// <1> 创建一个 Map 集合
Map<String, String> annotations = new LinkedHashMap<>();
// <1.1> 将指定注解的简称和全称之间的对应关系保存至第 `1` 步创建的 Map 中
addAnnotations(annotations);
// <1.2> 将 `1.1` 的 Map 转换成不可修改的 UnmodifiableMap 集合,赋值给 `annotations`
this.annotations = Collections.unmodifiableMap(annotations);
// <2> 创建一个 Map 集合
Map<String, ValueExtractor> valueExtractors = new LinkedHashMap<>();
// <2.1> 将指定注解的简称和对应的 ValueExtractor 对象保存至第 `2` 步创建的 Map 中
addValueExtractors(valueExtractors);
// <2.2> 将 `2.1` 的 Map 转换成不可修改的 UnmodifiableMap 集合,赋值给 `valueExtractors`
this.valueExtractors = Collections.unmodifiableMap(valueExtractors);
}
}

AbstractProcessor 是 JDK 1.6 引入的一个抽象类,支持在编译阶段进行处理,在构造器中做了以下事情:

  1. 创建一个 Map 集合

    1. 将指定注解的简称和全称之间的对应关系保存至第 1 步创建的 Map 中

      protected void addAnnotations(Map<String, String> annotations) {
      annotations.put("ConditionalOnClass", "org.springframework.boot.autoconfigure.condition.ConditionalOnClass");
      annotations.put("ConditionalOnBean", "org.springframework.boot.autoconfigure.condition.ConditionalOnBean");
      annotations.put("ConditionalOnSingleCandidate", "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate");
      annotations.put("ConditionalOnWebApplication", "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication");
      annotations.put("AutoConfigureBefore", "org.springframework.boot.autoconfigure.AutoConfigureBefore");
      annotations.put("AutoConfigureAfter", "org.springframework.boot.autoconfigure.AutoConfigureAfter");
      annotations.put("AutoConfigureOrder", "org.springframework.boot.autoconfigure.AutoConfigureOrder");
      }
    2. 1.1 的 Map 转换成不可修改的 UnmodifiableMap 集合,赋值给 annotations

  2. 创建一个 Map 集合

    1. 将指定注解的简称和对应的 ValueExtractor 对象保存至第 2 步创建的 Map 中

      private void addValueExtractors(Map<String, ValueExtractor> attributes) {
      attributes.put("ConditionalOnClass", new OnClassConditionValueExtractor());
      attributes.put("ConditionalOnBean", new OnBeanConditionValueExtractor());
      attributes.put("ConditionalOnSingleCandidate", new OnBeanConditionValueExtractor());
      attributes.put("ConditionalOnWebApplication", ValueExtractor.allFrom("type"));
      attributes.put("AutoConfigureBefore", ValueExtractor.allFrom("value", "name"));
      attributes.put("AutoConfigureAfter", ValueExtractor.allFrom("value", "name"));
      attributes.put("AutoConfigureOrder", ValueExtractor.allFrom("value"));
      }
    2. 2.1 的 Map 转换成不可修改的 UnmodifiableMap 集合,赋值给 valueExtractors

getSupportedSourceVersion 方法

返回支持的 Java 版本

@Override
public SourceVersion getSupportedSourceVersion() {
// 返回 Java 版本,默认 1.5
return SourceVersion.latestSupported();
}

process 方法

处理过程

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// <1> 遍历上面的几个 `@Conditional` 注解和几个定义自动配置类顺序的注解,依次进行处理
for (Map.Entry<String, String> entry : this.annotations.entrySet()) {
// <1.1> 对支持的注解进行处理,也就是找到所有标注了该注解的类,然后解析出该注解的值,保存至 Properties
// 例如 `类名.注解简称` => `注解中的值(逗号分隔)` 和 `类名` => `空字符串`,将自动配置类的信息已经对应注解的信息都保存起来
// 避免你每次启动 Spring Boot 应用都要去解析自动配置类上面的注解,是引入 `spring-boot-autoconfigure` 后可以从 `META-INF/spring-autoconfigure-metadata.properties` 文件中直接获取
// 这么一想,Spring Boot 设计的太棒了,所以你自己写的 Spring Boot Starter 中的自动配置模块也可以引入这个 Spring Boot 提供的插件
process(roundEnv, entry.getKey(), entry.getValue());
}
// <2> 如果处理完成
if (roundEnv.processingOver()) {
try {
// <2.1> 将 Properties 写入 `META-INF/spring-autoconfigure-metadata.properties` 文件
writeProperties();
}
catch (Exception ex) {
throw new IllegalStateException("Failed to write metadata", ex);
}
}
// <3> 返回 `false`
return false;
}

过程如下:

  1. 遍历上面的几个 @Conditional 注解和几个定义自动配置类顺序的注解,依次进行处理

    1. 调用 process(..) 重载方法,对支持的注解进行处理,也就是找到所有标注了该注解的类,然后解析出该注解的值,保存至 Properties
  2. 如果处理完成

    1. 调用 writeProperties() 方法,将 Properties 写入 META-INF/spring-autoconfigure-metadata.properties 文件

      private void writeProperties() throws IOException {
      if (!this.properties.isEmpty()) {
      FileObject file = this.processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", PROPERTIES_PATH);
      try (OutputStream outputStream = file.openOutputStream()) {
      this.properties.store(outputStream, null);
      }
      }
      }

上面的第 1.1 步处理后的 Properties 包含以下内容:

  • 自动配置类的类名.注解简称 --> 注解中的值(逗号分隔)
  • 自动配置类的类名 --> 空字符串

通过后续写入的文件,避免你每次启动 Spring Boot 应用都要去解析自动配置类上面的注解,从而提高应用启动时的效率

这么一想,Spring Boot 设计的太棒了,所以你自己写的 Spring Boot Starter 中的自动配置模块也可以引入这个 Spring Boot 提供的插件

process 重载方法

private void process(RoundEnvironment roundEnv, String propertyKey, String annotationName) {
// <1> 获取到这个注解名称对应的 Java 类型
TypeElement annotationType = this.processingEnv.getElementUtils().getTypeElement(annotationName);
if (annotationType != null) {
// <2> 如果存在该注解,则从 RoundEnvironment 中获取标注了该注解的所有 Element 元素,进行遍历
for (Element element : roundEnv.getElementsAnnotatedWith(annotationType)) {
// <2.1> 获取这个 Element 元素 innermost 最深处的 Element
Element enclosingElement = element.getEnclosingElement();
// <2.2> 如果最深处的 Element 的类型是 PACKAGE 包,那么表示这个元素是一个类,则进行处理
if (enclosingElement != null && enclosingElement.getKind() == ElementKind.PACKAGE) {
// <2.2.1> 解析这个类上面 `annotationName` 注解的信息,并保存至 `properties` 中
processElement(element, propertyKey, annotationName);
}
}
}
}

过程如下:

  1. 获取到这个注解名称对应的 Java 类型
  2. 如果存在该注解,则从 RoundEnvironment 中获取标注了该注解的所有 Element 元素,进行遍历
    1. 获取这个 Element 元素 innermost 最深处的 Element
    2. 如果最深处的 Element 的类型是 PACKAGE 包,那么表示这个元素是一个类,则进行处理
      1. 调用 processElement(..) 方法,解析这个类上面 annotationName 注解的信息,并保存至 properties

processElement 方法

private void processElement(Element element, String propertyKey, String annotationName) {
try {
// <1> 获取这个类的名称
String qualifiedName = Elements.getQualifiedName(element);
// <2> 获取这个类上面的 `annotationName` 类型的注解信息
AnnotationMirror annotation = getAnnotation(element, annotationName);
if (qualifiedName != null && annotation != null) {
// <3> 获取这个注解中的值
List<Object> values = getValues(propertyKey, annotation);
// <4> 往 `properties` 中添加 `类名.注解简称` => `注解中的值(逗号分隔)`
this.properties.put(qualifiedName + "." + propertyKey, toCommaDelimitedString(values));
// <5> 往 `properties` 中添加 `类名` => `空字符串`
this.properties.put(qualifiedName, "");
}
}
catch (Exception ex) {
throw new IllegalStateException("Error processing configuration meta-data on " + element, ex);
}
}

过程如下:

  1. 获取这个类的名称

  2. 获取这个类上面的 annotationName 类型的注解信息

    private AnnotationMirror getAnnotation(Element element, String type) {
    if (element != null) {
    for (AnnotationMirror annotation : element.getAnnotationMirrors()) {
    if (type.equals(annotation.getAnnotationType().toString())) {
    return annotation;
    }
    }
    }
    return null;
    }
  3. 获取这个注解中的值

    private List<Object> getValues(String propertyKey, AnnotationMirror annotation) {
    // 获取该注解对应的 value 抽取器
    ValueExtractor extractor = this.valueExtractors.get(propertyKey);
    if (extractor == null) {
    return Collections.emptyList();
    }
    // 获取这个注解中的值,并返回
    return extractor.getValues(annotation);
    }
  4. properties 中添加 类名.注解简称 --> 注解中的值(逗号分隔)

  5. properties 中添加 类名 --> 空字符串

总结

本文分析了 @SpringBootApplication 组合注解,它是 @SpringBootConfiguration@ComponentScan@EnableAutoConfiguration 几个注解的组合注解,对于前两个注解我想你并不陌生,分析 Spring 源码的时候差不多已经讲过,最后一个注解则是 Spring Boot 自动配置功能的驱动注解,也是本文讲述的一个重点。

@EnableAutoConfiguration 注解的实现原理并不复杂,借助于 @Import 注解,从所有 META-INF/spring.factories 文件中找到 org.springframework.boot.autoconfigure.EnableAutoConfiguration 对应的值,例如:

会将这些自动配置类作为一个 Bean 尝试注入到 Spring IoC 容器中,注入的时候 Spring 会通过 @Conditional 注解判断是否符合条件,因为并不是所有的自动配置类都满足条件。当然,Spring Boot 对 @Conditional 注解进行了扩展,例如 @ConditionalOnClass 可以指定必须存在哪些 Class 对象才注入这个 Bean。

Spring Boot 会借助 spring-boot-autoconfigure-processor 工具模块在编译阶段将自己的自动配置类的注解元信息保存至一个 properties 文件中,避免每次启动应用都要去解析这么多自动配置类上面的注解。同时会通过几个过滤器根据这个 properties 文件过滤掉一些自动配置类,具体怎么过滤的会在下面文章讲到。

好了,总结下来就是 Spring Boot 会从 META-INF/spring.factories 文件中找到配置的自动配置类,然后根据 Condition 条件进行注入,如果注入的话则可以通过 @EnableXxx 模块驱动注解驱动某个模块,例如 Spring AOP 模块。那么关于 Spring Boot 对 Spring Condition 的扩展在下篇文章进行分析。

精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解的更多相关文章

  1. 精尽Spring Boot源码分析 - 文章导读

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  2. 精尽Spring Boot源码分析 - Condition 接口的扩展

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  3. 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  4. 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  5. 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  6. 精尽Spring Boot源码分析 - 配置加载

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  7. 精尽Spring Boot源码分析 - 日志系统

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  8. 精尽Spring Boot源码分析 - @ConfigurationProperties 注解的实现

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

  9. 精尽Spring Boot源码分析 - 序言

    该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...

随机推荐

  1. C#·JSON的处理和解析

    阅文时长 | 0.34分钟 字数统计 | 309.6字符 主要内容 | 1.引言&背景 2.声明与参考资料 『C#·JSON的处理和解析』 编写人 | SCscHero 编写时间 | 2021 ...

  2. [刷题] 416 Partition Equal Subset Sum

    要求 非空数组的所有数字都是正整数,是否可以将这个数组的元素分成两部分,使得每部分的数字和相等 最多200个数字,每个数字最大为100 示例 [1,5,11,5],返回 true [1,2,3,5], ...

  3. 用户添加到sudoer列表## Allow root to run any commands anywhere root ALL=(ALL) ALL Iron ALL=(ALL) ALL

    将用户添加到sudoer列表 李序锴关注 2017.12.20 15:03:25字数 605阅读 4,067 默认情况下,linux没有将当前用户列入到sudoer列表中(在redhat系列的linu ...

  4. systemctl list-unit-files

    [CentOS]centos7上查看服务开机启动列表 systemctl list-unit-files centos7上查看服务开机启动列表 命令: systemctl list-unit-file ...

  5. Linux Test Project(一)

    http://www.vimlinux.com/lipeng/2014/09/12/ltp/ Testing Linux, one syscall at a time. LTP是从SGI开始的,后由I ...

  6. 查看 swappiness 值

    Swap的使用频率  发表于 2017-06-02 |  分类于 Linux |  评论数: 通过调整swappiness的值, 可以调整系统使用 swap 的频率 该值越小, 表示越大限度的使用物理 ...

  7. Win10 禁用摄像头的方法及注意事项

    Win10 禁用摄像头的方法及注意事项 windows教程 2020-03-04  223 最新的Windows10系统中应该如何禁用摄像头呢?下面MS酋长与大家分享一下.当然,如果你说用个便利贴把摄 ...

  8. 攻防世界(八)web2

    攻防世界系列:web2 1.代码审计  知识补充: strrev(string):反转字符串 strlen(string):字符串长度 substr(string,start,length):截取字符 ...

  9. SSH连接自动断开的解决方法(deb/rpm)

    ######### 修改后的: ## # tail -f -n 20 sshd_config#MaxStartups 10:30:60#Banner /etc/issue.net # Allow cl ...

  10. 013.Python的文件操作

    一 文件操作 fp = open("打开的文件",mode="模式选择",encoding="编码集") open 函数 返回一个文件io对 ...