虽然 Spring Boot 在日常开发中已经算是 Java 领域的标配了,但仔细回想一下面试经历,与 Spring Boot 直接相关的面试题其实并不算多。本质上,Spring Boot 仍是曾经 SSM 那一套,只是通过各种 starter 简化了配置,很多问题最终还是要回归到 Spring 中去解答。不过这并不意味着 Spring Boot 没有可问的点,其中有一个非常经典的面试题就是:Spring Boot 的启动原理究竟是什么?
在此之前我断断续续聊过相关内容,也带领大家自定义过一个 starter,相信对 starter 的原理已经有了一定基础。所以今天这篇文章会跳过一些过于琐碎的细节,专注于把启动流程系统性地梳理清楚。如果你对更细的环节感兴趣,可以随时翻阅云栈社区上往期的 Spring 系列文章。
一、@SpringBootApplication
要说 Spring Boot 的自动化配置,绕不开项目的启动类注解 @SpringBootApplication,这是整个 Spring Boot 世界的起点。先来看这个注解的定义:
@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 组合了多个注解的功能:
- 前四个是元注解,这里不做讨论。
- 第五个
@SpringBootConfiguration 是一个支持配置类的注解,也不展开。
- 第六个
@EnableAutoConfiguration 表示开启自动化配置,这是今天要聊的重点。
- 第七个
@ComponentScan 是包扫描注解。为什么 Spring Boot 项目中的 Bean 只要放对位置就会被自动扫描到,就和它有关。
别看这里注解多,真正由 Spring Boot 提供的其实只有两个:@SpringBootConfiguration 和 @EnableAutoConfiguration。其余注解在 Spring Boot 出现之前就已经存在多年了。
二、@EnableAutoConfiguration
接下来看看 @EnableAutoConfiguration 是如何实现自动化配置的。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}
这个注解起关键作用的就是两个东西:
@AutoConfigurationPackage:表示自动扫描各种第三方的注解。之前我已经详细分析过这个注解的作用,传送门:@AutoConfigurationPackage 和 @ComponentScan 有何区别?
@Import:导入 AutoConfigurationImportSelector 配置类,这个类内部负责加载各种自动化配置类。
三、AutoConfigurationImportSelector
AutoConfigurationImportSelector 类中的方法比较多,入口是 process 方法,我们就从这里开始看:
@Override
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()));
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
从类名就能看出来,真正加载自动化配置对象的是这一行:
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
getAutoConfigurationEntry 方法实际上就在当前类中,来看实现:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
这里的方法命名都做到了见名知意,日常开发中很值得学习。下面逐个拆解关键方法。
3.1 isEnabled
首先调用 isEnabled 方法判断自动化配置是否开启。即使项目中引入了 spring-boot-starter-xxx,也可以通过配置 spring.boot.enableautoconfiguration=false 来关闭所有自动化配置。
相关源码如下:
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(
EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}
3.2 getCandidateConfigurations
接下来调用 获取所有候选的自动化配置类 方法,这些候选配置类主要来自两个地方:
- 在自定义 starter 时,需要在
classpath:META-INF/spring.factories 中定义出所有的自动化配置类,这是来源一。
- Spring Boot 自带的自动化配置类,位于
spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中。
相关源码如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
.forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
加载到的自动化配置类全路径被存入 configurations 对象,获取途径有两个:
- 调用
SpringFactoriesLoader.loadFactoryNames 方法,本质上就是去加载 META-INF/spring.factories 文件,里面定义了大量的自动化配置类全路径。
- 调用
ImportCandidates.load 方法,加载 spring-boot-autoconfigure-3.0.6.jar!\META-INF\spring\org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中的配置类。
如果两个地方都没有加载到任何自动化配置类,就会抛出异常。
3.3 removeDuplicates
removeDuplicates 方法负责移除候选配置类中重复的项,思路很有意思,就是用一个 LinkedHashSet 中转一下:
protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}
源码里确实藏着不少这种简洁有效的解决思路。
3.4 getExclusions
getExclusions 方法表示获取所有被排除的自动化配置类,可以从三个地方获取:
- 当前注解的
exclude 属性。
- 当前注解的
excludeName 属性。
application.properties 配置文件中的 spring.autoconfigure.exclude 属性。
来看相关源码:
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
excluded.addAll(asList(attributes, "exclude"));
excluded.addAll(asList(attributes, "excludeName"));
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}
正好和上面说的三点一一对应。
3.5 checkExcludedClasses
这个方法用来检查所有被排除的自动化配置类。由于 Spring Boot 中的自动化配置类可以自定义,并不需要统一实现某个接口或继承某个类,所以写排除类时如果写错了,编译阶段是校验不出来的,比如这样:
@SpringBootApplication(exclude = HelloController.class)
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
由于 HelloController 并不是一个自动化配置类,项目启动时就会报错:

这个异常就来自 checkExcludedClasses 方法,来看实现:
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
List<String> invalidExcludes = new ArrayList<>(exclusions.size());
for (String exclusion : exclusions) {
if (ClassUtils.isPresent(exclusion, getClass().getClassLoader())
&& !configurations.contains(exclusion)) {
invalidExcludes.add(exclusion);
}
}
if (!invalidExcludes.isEmpty()) {
handleInvalidExcludes(invalidExcludes);
}
}
protected void handleInvalidExcludes(List<String> invalidExcludes) {
StringBuilder message = new StringBuilder();
for (String exclude : invalidExcludes) {
message.append("\t- ").append(exclude).append(String.format("%n"));
}
throw new IllegalStateException(String.format(
"The following classes could not be excluded because they are not auto-configuration classes:%n%s",
message));
}
在 checkExcludedClasses 方法中,会先找出所有位于当前类路径下、却不包含在 configurations 中的被排除类。由于 configurations 中已经是全部自动化配置类,所以这些不存在的类都是有问题的,并不是自动化配置类。这些有问题的类会被收集到 invalidExcludes 中,再进入额外的处理流程。
所谓额外处理,就是在 handleInvalidExcludes 方法中抛出异常,前面截图里的异常就来自这里。
3.6 removeAll
这个方法只做一件事:从 configurations 中移除掉那些被排除的自动化配置类。configurations 本身是 List,exclusions 是 Set,直接调用 removeAll 即可。
3.7 filter
现在所有自动化配置类都已经加载完成了,但这些配置类并不会全部生效,具体是否生效还要看项目是否引入了对应的依赖。
举个例子,现在加载的配置里包含 RedisAutoConfiguration,它是用来自动配置 Redis 的,但如果项目中没有使用 Redis,这个自动化配置类就不会生效。这个过程正是由 getConfigurationClassFilter().filter(configurations) 完成的。
先说预备知识:
由于项目中的自动化配置类特别多,每个配置类都会依赖别的类,当依赖的类存在时,该配置类才会生效。这一堆互相之间的依赖关系存在于 spring-boot-autoconfigure-3.0.6.jar!/META-INF/spring-autoconfigure-metadata.properties 文件之中。随便举一个该文件中的配置:
org.springframework.boot.autoconfigure.amqp.RabbitAnnotationDrivenConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.annotation.EnableRabbit
这表示 RabbitAnnotationDrivenConfiguration 生效的必备条件是当前项目类路径下存在 org.springframework.amqp.rabbit.annotation.EnableRabbit。
再看看 RabbitAnnotationDrivenConfiguration 类的注解:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(EnableRabbit.class)
class RabbitAnnotationDrivenConfiguration {
}
这个类和配置文件中的内容完全一致。
搞懂这个预备知识,后面的内容就好理解了。
先看 getConfigurationClassFilter 方法,它负责获取所有的过滤器:
private ConfigurationClassFilter getConfigurationClassFilter() {
if (this.configurationClassFilter == null) {
List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
for (AutoConfigurationImportFilter filter : filters) {
invokeAwareMethods(filter);
}
this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
}
return this.configurationClassFilter;
}
这里获取到的过滤器都是 AutoConfigurationImportFilter 类型的,这个类型只有三个实例,如下图所示:

从三个实例的名字就能大致看出各自的作用:
OnClassCondition:条件注解 @ConditionalOnClass 的判定条件,用来判断当前 classpath 下是否存在某个类。
OnWebApplicationCondition:条件注解 ConditionalOnWebApplication 的判定条件,用来判断当前系统环境是否是一个 Web 环境。
OnBeanCondition:条件注解 @ConditionalOnBean 的判定条件,用来判断当前系统下是否存在某个 Bean。
获取到的三个 AutoConfigurationImportFilter 过滤器就是上面这三个。接下来执行 filter 方法:
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
for (AutoConfigurationImportFilter filter : this.filters) {
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) {
return configurations;
}
List<String> result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
if (candidate != null) {
result.add(candidate);
}
}
return result;
}
这里遍历三个过滤器,分别调用各自的 match 方法和 144 个自动化配置类进行匹配。如果某个自动化配置类所需的条件得到满足,match 数组对应位置就是 true,否则为 false。
然后遍历 match 数组,将不满足条件的自动化配置类置为 null,最后再把这些 null 移除掉。
这样就拿到了真正需要执行自动化配置的类。
最后一句 fireAutoConfigurationImportEvents 是触发自动化配置类导入事件,这里就不展开了。
当这些自动化配置类加载进来之后,接下来就是各种条件注解来决定这些配置类是否生效了,这部分相对简单,之前也讲过多次,不再赘述。
经过上面的梳理,相信你对 Spring Boot 启动原理已经有了一个整体的认知。总结一下核心链路:从 @SpringBootApplication 出发,经由 @EnableAutoConfiguration 导入 AutoConfigurationImportSelector,在 getAutoConfigurationEntry 方法中完成候选配置类的加载、去重、排除、过滤四个关键步骤,最终得到实际生效的自动化配置类列表。搞懂了这条主线,再去深挖某个具体 starter 的实现就会顺畅很多。