文章目录
- SpringBoot是如何实现自动配置的? SpringBoot源码(四)
- 1 前言
- 2 @SpringBootApplication注解
- 3 如何去找SpringBoot自动配置实现逻辑的入口方法?
- 4 分析SpringBoot自动配置原理
- 4.1 分析自动配置的主要逻辑
- 4.2 有选择的导入自动配置类
- 5 AutoConfigurationImportFilter
- 5.1 OnClassCondition
- 5.1.1 createOutcomesResolver
- 5.1.2 new StandardOutcomesResolver
- 5.1.3 StandardOutcomesResolver.resolveOutcomes方法
- 5.1.4 ThreadedOutcomesResolver.resolveOutcomes方法
- 5.2 OnBeanCondition和OnWebApplicationCondition
- 6 AutoConfigurationImportListener
- 7 AutoConfigurationPackages
- 8 小结
SpringBoot是如何实现自动配置的? SpringBoot源码(四)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE
1 前言
本篇接 助力SpringBoot自动配置的条件注解原理揭秘 SpringBoot源码(三)
温故而知新,我们来简单回顾一下上篇的内容,上一篇我们分析了SpringBoot的条件注解@ConditionalOnXxx的相关源码,现挑重点总结如下:
SpringBoot的所有@ConditionalOnXxx的条件类OnXxxCondition都是继承于SpringBootCondition基类,而SpringBootCondition又实现了Condition接口。
SpringBootCondition基类主要用来打印一些条件注解评估报告的日志,这些条件评估信息全部来源于其子类注解条件类OnXxxCondition,因此其也抽象了一个模板方法getMatchOutcome留给子类去实现来评估其条件注解是否符合条件。
前一篇我们也还有一个重要的知识点还没分析,那就是跟过滤自动配置类逻辑有关的AutoConfigurationImportFilter接口,这篇文章我们来填一下这个坑。
前面我们分析了跟SpringBoot的自动配置息息相关内置条件注解@ConditionalOnXxx后,现在我们就开始来撸SpringBoot自动配置的相关源码了。
2 @SpringBootApplication注解
在开始前,我们先想一下,SpringBoot为何一个标注有@SpringBootApplication注解的启动类通过执行一个简单的run方法就能实现SpringBoot大量Starter的自动配置呢? 其实SpringBoot的自动配置就跟@SpringBootApplication这个注解有关,我们先来看下其这个注解的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// ...省略非关键代码
}
@SpringBootApplication标注了很多注解,我们可以看到其中跟SpringBoot自动配置有关的注解就有一个即@EnableAutoConfiguration,因此,可以肯定的是SpringBoot的自动配置肯定跟@EnableAutoConfiguration息息相关(其中@ComponentScan注解的excludeFilters属性也有一个类AutoConfigurationExcludeFilter,这个类跟自动配置也有点关系,但不是我们关注的重点)。 现在我们来打开@EnableAutoConfiguration注解的源码:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {
};
String[] excludeName() default {
};
}
看到@EnableAutoConfiguration注解又标有@AutoConfigurationPackage和@Import(AutoConfigurationImportSelector.class)两个注解,顾名思义,@AutoConfigurationPackage注解肯定跟自动配置的包有关,而AutoConfigurationImportSelector则是跟SpringBoot的自动配置选择导入有关(Spring中的ImportSelector是用来导入配置类的,通常是基于某些条件注解@ConditionalOnXxxx来决定是否导入某个配置类)。
因此,可以看出AutoConfigurationImportSelector类是我们本篇的重点,因为SpringBoot的自动配置肯定有一个配置类,而这个配置类的导入则需要靠AutoConfigurationImportSelector这个哥们来实现。
接下来我们重点来看AutoConfigurationImportSelector这个类,完了我们再简单分析下@AutoConfigurationPackage这个注解的逻辑。
3 如何去找SpringBoot自动配置实现逻辑的入口方法?
可以肯定的是SpringBoot的自动配置的逻辑肯定与AutoConfigurationImportSelector这个类有关,那么我们该如何去找到SpringBoot自动配置实现逻辑的入口方法呢?
在找SpringBoot自动配置实现逻辑的入口方法前,我们先来看下AutoConfigurationImportSelector的相关类图,好有个整体的理解。看下图:
可以看到AutoConfigurationImportSelector重点是实现了DeferredImportSelector接口和各种Aware接口,然后DeferredImportSelector接口又继承了ImportSelector接口。
自然而然的,我们会去关注AutoConfigurationImportSelector复写DeferredImportSelector接口的实现方法selectImports方法,因为selectImports方法跟导入自动配置类有关,而这个方法往往是程序执行的入口方法。经过调试发现selectImports方法很具有迷惑性,selectImports方法跟自动配置相关的逻辑有点关系,但实质关系不大。
此时剧情的发展好像不太符合常理,此时我们又该如何来找到自动配置逻辑有关的入口方法呢?
最简单的方法就是在AutoConfigurationImportSelector类的每个方法都打上断点,然后调试看先执行到哪个方法。但是我们可以不这么做,我们回想下,自定义一个Starter的时候我们是不是要在spring.factories配置文件中配置
EnableAutoConfiguration=XxxAutoConfiguration
因此可以推断,SpringBoot的自动配置原理肯定跟从spring.factories配置文件中加载自动配置类有关,于是结合AutoConfigurationImportSelector的方法注释,我们找到了getAutoConfigurationEntry方法。于是我们在这个方法里面打上一个断点,此时通过调用栈帧来看下更上层的入口方法在哪里,然后我们再从跟自动配置相关的更上层的入口方法开始分析。
通过图我们可以看到,跟自动配置逻辑相关的入口方法在DeferredImportSelectorGrouping类的getImports方法处,因此我们就从DeferredImportSelectorGrouping类的getImports方法来开始分析SpringBoot的自动配置源码好了。
4 分析SpringBoot自动配置原理
既然找到ConfigurationClassParser.getImports()方法是自动配置相关的入口方法,那么下面我们就来真正分析SpringBoot自动配置的源码了。
先看一下getImports方法代码:
// ConfigurationClassParser.java
public Iterable<Group.Entry> getImports() {
// 遍历DeferredImportSelectorHolder对象集合deferredImports,deferredImports集合装了各种ImportSelector,当然这里装的是AutoConfigurationImportSelector
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
// 【1】,利用AutoConfigurationGroup的process方法来处理自动配置的相关逻辑,决定导入哪些配置类(这个是我们分析的重点,自动配置的逻辑全在这了)
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
// 【2】,经过上面的处理后,然后再进行选择导入哪些配置类
return this.group.selectImports();
}
标【1】处的的代码是我们分析的重中之重,自动配置的相关的绝大部分逻辑全在这里了,将在4.1 分析自动配置的主要逻辑深入分析。那么this.group.process(deferredImport.getConfigurationClass().getMetadata(), deferredImport.getImportSelector());主要做的事情就是在this.group即AutoConfigurationGroup对象的process方法中,传入的AutoConfigurationImportSelector对象来选择一些符合条件的自动配置类,过滤掉一些不符合条件的自动配置类,就是这么个事情,无他。
注:
- AutoConfigurationGroup:是AutoConfigurationImportSelector的内部类,主要用来处理自动配置相关的逻辑,拥有process和selectImports方法,然后拥有entries和autoConfigurationEntries集合属性,这两个集合分别存储被处理后的符合条件的自动配置类,我们知道这些就足够了;
- AutoConfigurationImportSelector:承担自动配置的绝大部分逻辑,负责选择一些符合条件的自动配置类;
- metadata:标注在SpringBoot启动类上的@SpringBootApplication注解元数据
标【2】的this.group.selectImports的方法主要是针对前面的process方法处理后的自动配置类再进一步有选择的选择导入,将在4.2 有选择的导入自动配置类这小节深入分析。
4.1 分析自动配置的主要逻辑
这里继续深究前面 4 分析SpringBoot自动配置原理这节标【1】处的 this.group.process方法是如何处理自动配置相关逻辑的。
// AutoConfigurationImportSelector$AutoConfigurationGroup.java
// 这里用来处理自动配置类,比如过滤掉不符合匹配条件的自动配置类
public void process(AnnotationMetadata annotationMetadata,
DeferredImportSelector deferredImportSelector) {
Assert.state(
deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// 【1】,调用getAutoConfigurationEntry方法得到自动配置类放入autoConfigurationEntry对象中
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(getAutoConfigurationMetadata(),
annotationMetadata);
// 【2】,又将封装了自动配置类的autoConfigurationEntry对象装进autoConfigurationEntries集合
this.autoConfigurationEntries.add(autoConfigurationEntry);
// 【3】,遍历刚获取的自动配置类
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
// 这里符合条件的自动配置类作为key,annotationMetadata作为值放进entries集合
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
上面代码中我们再来看标【1】的方法getAutoConfigurationEntry,这个方法主要是用来获取自动配置类有关,承担了自动配置的主要逻辑。直接上代码:
// AutoConfigurationImportSelector.java
// 获取符合条件的自动配置类,避免加载不必要的自动配置类从而造成内存浪费
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
// 获取是否有配置spring.boot.enableautoconfiguration属性,默认返回true
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获得@Congiguration标注的Configuration类即被审视introspectedClass的注解数据,
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 将会获取到exclude = FreeMarkerAutoConfiguration.class和excludeName=""的注解数据
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 【1】得到spring.factories文件配置的所有自动配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 利用LinkedHashSet移除重复的配置类
configurations = removeDuplicates(configurations);
// 得到要排除的自动配置类,比如注解属性exclude的配置类
// 比如:@SpringBootApplication(exclude = FreeMarkerAutoConfiguration.class)
// 将会获取到exclude = FreeMarkerAutoConfiguration.class的注解数据
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查要被排除的配置类,因为有些不是自动配置类,故要抛出异常
checkExcludedClasses(