精尽Spring Boot源码分析 - 剖析 @SpringBootApplication 注解
该系列文章是笔者在学习 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
注解的值有三种情况:
该 Class 对象实现了
ImportSelector
接口,调用它的selectImports(..)
方法获取需要被处理的 Class 对象的名称,也就是可以将它们作为一个 Bean 被 Spring IoC 管理- 该 Class 对象实现了
DeferredImportSelector
接口,和上者的执行时机不同,在所有配置类处理完后再执行,且支持@Order
排序
- 该 Class 对象实现了
该 Class 对象实现了
ImportBeanDefinitionRegistrar
接口,会调用它的registerBeanDefinitions(..)
方法,自定义地往 BeanDefinitionRegistry 注册中心注册 BeanDefinition(Bean 的前身)该 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());
}
过程如下:
- 如果通过
spring.boot.enableautoconfiguration
配置关闭了自动配置功能,那么直接返回一个空数组 - 解析
META-INF/spring-autoconfigure-metadata.properties
文件,生成一个 AutoConfigurationMetadata 自动配置类元数据对象 - 调用
getAutoConfigurationEntry(..)
方法, 从所有的META-INF/spring.factories
文件中找到@EnableAutoConfiguration
注解对应的类(需要自动配置的类),会进行过滤处理,然后封装在一个AutoConfigurationEntry
对象中 - 返回所有需要自动配置的类
上面第 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,找到了答案
在引入
spring-boot-autoconfigure-processor
工具模块依赖后,其中会通过 Java SPI 机制引入 AutoConfigureAnnotationProcessor 注解处理器在编译阶段进行相关处理其中
spring-boot-autoconfigure
模块会引入该工具模块(不具有传递性),那么 Spring Boot 在编译spring-boot-autoconfigure
这个jar
包的时候,在编译阶段会扫描到带有@ConditionalOnClass
等注解的.class
文件,也就是自动配置类,然后将自动配置类的一些信息保存至META-INF/spring-autoconfigure-metadata.properties
文件中文件中保存了
自动配置类类名.注解简称
-->注解中的值(逗号分隔)
,自动配置类类名
-->空字符串
当然,你自己写的 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);
}
过程如下:
如果通过
spring.boot.enableautoconfiguration
配置关闭了自动配置功能,则返回一个“空”的对象获取
@EnableAutoConfiguration
注解的配置信息从所有的
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 提供的一个类
对所有的自动配置类进行去重
protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}
获取需要排除的自动配置类,可通过
@EnableAutoConfiguration
注解的exclude
和excludeName
配置,也可以通过spring.autoconfigure.exclude
配置处理
exclusions
中特殊的类名称,保证能够排除它从
configurations
中将exclusions
需要排除的自动配置类移除调用
filter(..)
方法, 目的就是过滤掉一些不符合 Condition 条件的自动配置类,和在 1. selectImports 方法 小节中讲到的性能优化有关哦从
META-INF/spring.factories
找到所有的 AutoConfigurationImportListener 事件监听器,触发每个监听器去处理 AutoConfigurationImportEvent 事件,该事件中包含了configurations
和exclusions
Spring Boot 中配置了一个监听器,目的就是将
configurations
和exclusions
保存至 AutoConfigurationImportEvent 对象中,并注册到 IoC 容器中,名称为autoConfigurationReport
,这样一来我们可以注入这个 Bean 获取到自动配置类信息将所有的自动配置类封装成一个 AutoConfigurationEntry 对象,并返回
整个过程不复杂,关键在于上面的第 3
步和第 8
步,先从所有的 META-INF/spring.factories
文件中找到 @EnableAutoConfiguration
注解对应的类(需要自动配置的类),然后进行过滤
3. filter 方法
filter(List<String>, AutoConfigurationMetadata)
方法,过滤一些自动配置类
我们得先知道这两个入参:
- 所有的自动配置类名称
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);
}
过程如下:
将自动配置类保存至
candidates
数组中从
META-INF/spring.factories
找到所有的AutoConfigurationImportFilter
对candidates
进行过滤处理# 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
- Aware 回调
- 对
candidates
进行匹配处理,获取所有的匹配结果,注意,这里传入了AutoConfigurationMetadata
对象 - 遍历匹配结果,将不匹配的自动配置类至空
如果没有不匹配的结果则全部返回
获取到所有匹配的自动配置类,并返回
关键在于上面的第 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 引入的一个抽象类,支持在编译阶段进行处理,在构造器中做了以下事情:
创建一个 Map 集合
将指定注解的简称和全称之间的对应关系保存至第
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");
}
将
1.1
的 Map 转换成不可修改的 UnmodifiableMap 集合,赋值给annotations
创建一个 Map 集合
将指定注解的简称和对应的 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.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;
}
过程如下:
遍历上面的几个
@Conditional
注解和几个定义自动配置类顺序的注解,依次进行处理- 调用
process(..)
重载方法,对支持的注解进行处理,也就是找到所有标注了该注解的类,然后解析出该注解的值,保存至 Properties
- 调用
如果处理完成
调用
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);
}
}
}
}
过程如下:
- 获取到这个注解名称对应的 Java 类型
- 如果存在该注解,则从 RoundEnvironment 中获取标注了该注解的所有 Element 元素,进行遍历
- 获取这个 Element 元素 innermost 最深处的 Element
- 如果最深处的 Element 的类型是 PACKAGE 包,那么表示这个元素是一个类,则进行处理
- 调用
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);
}
}
过程如下:
获取这个类的名称
获取这个类上面的
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;
}
获取这个注解中的值
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);
}
往
properties
中添加类名.注解简称
-->注解中的值(逗号分隔)
往
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 注解的更多相关文章
- 精尽Spring Boot源码分析 - 文章导读
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - Condition 接口的扩展
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - SpringApplication 启动类的启动过程
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 内嵌Tomcat容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 支持外部 Tomcat 容器的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 配置加载
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 日志系统
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - @ConfigurationProperties 注解的实现
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- 精尽Spring Boot源码分析 - 序言
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
随机推荐
- C#·JSON的处理和解析
阅文时长 | 0.34分钟 字数统计 | 309.6字符 主要内容 | 1.引言&背景 2.声明与参考资料 『C#·JSON的处理和解析』 编写人 | SCscHero 编写时间 | 2021 ...
- [刷题] 416 Partition Equal Subset Sum
要求 非空数组的所有数字都是正整数,是否可以将这个数组的元素分成两部分,使得每部分的数字和相等 最多200个数字,每个数字最大为100 示例 [1,5,11,5],返回 true [1,2,3,5], ...
- 用户添加到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 ...
- systemctl list-unit-files
[CentOS]centos7上查看服务开机启动列表 systemctl list-unit-files centos7上查看服务开机启动列表 命令: systemctl list-unit-file ...
- Linux Test Project(一)
http://www.vimlinux.com/lipeng/2014/09/12/ltp/ Testing Linux, one syscall at a time. LTP是从SGI开始的,后由I ...
- 查看 swappiness 值
Swap的使用频率 发表于 2017-06-02 | 分类于 Linux | 评论数: 通过调整swappiness的值, 可以调整系统使用 swap 的频率 该值越小, 表示越大限度的使用物理 ...
- Win10 禁用摄像头的方法及注意事项
Win10 禁用摄像头的方法及注意事项 windows教程 2020-03-04 223 最新的Windows10系统中应该如何禁用摄像头呢?下面MS酋长与大家分享一下.当然,如果你说用个便利贴把摄 ...
- 攻防世界(八)web2
攻防世界系列:web2 1.代码审计 知识补充: strrev(string):反转字符串 strlen(string):字符串长度 substr(string,start,length):截取字符 ...
- SSH连接自动断开的解决方法(deb/rpm)
######### 修改后的: ## # tail -f -n 20 sshd_config#MaxStartups 10:30:60#Banner /etc/issue.net # Allow cl ...
- 013.Python的文件操作
一 文件操作 fp = open("打开的文件",mode="模式选择",encoding="编码集") open 函数 返回一个文件io对 ...