SpringBoot自动配置原理-从main方法到EnableAutoConfiguration完全解析
前言
“面试官问:Spring Boot 是怎么实现自动配置的?你说就是加个 @SpringBootApplication 注解…然后就没有然后了。”
这是很多 Java 开发者面试时的尴尬瞬间。我们天天用 Spring Boot,却很少深究它背后的魔法是怎么实现的。
今天这篇文章,我会从你写的第一行 main 方法开始,一步步追踪 Spring Boot 自动配置的完整链路,让你真正理解"约定优于配置"的精髓。

一、从 main 方法说起
每个 Spring Boot 项目都从这几行代码开始:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
看起来平平无奇,但这里藏着两个关键点:
📌 关键点1:@SpringBootApplication 注解
这个注解是一个"三合一"组合:
@SpringBootConfiguration // 等同于 @Configuration
@EnableAutoConfiguration // 🔥 核心!开启自动配置
@ComponentScan // 开启组件扫描
public @interface SpringBootApplication {
// ...
}
📌 关键点2:SpringApplication.run()
这是启动的真正入口,它会完成以下工作:
- 推断应用类型(Servlet/Reactive)
- 加载所有 Initializer 和 Listener
- 创建并刷新 ApplicationContext
二、@EnableAutoConfiguration 的秘密
@EnableAutoConfiguration 是自动配置的灵魂,它的定义如下:
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
🔥 核心机制:@Import(AutoConfigurationImportSelector.class)
这个 AutoConfigurationImportSelector 就是自动配置的"搬运工",它会:
-
读取配置文件:扫描
META-INF/spring.factories(Spring Boot 2.x)或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3.x) -
加载候选配置类:把所有
EnableAutoConfiguration对应的类加载进来 -
条件过滤:通过
@Conditional系列注解决定哪些配置真正生效
三、spring.factories 文件长什么样?
以 spring-boot-autoconfigure 包为例,META-INF/spring.factories 内容类似:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
# ... 还有100多个
Spring Boot 3.x 改用了新文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
格式也更简洁,每行一个类名:
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
四、AutoConfigurationImportSelector 源码解析
前面说了 AutoConfigurationImportSelector 会读取配置文件,那它具体是怎么读的?
4.1 核心调用链
selectImports() // 入口方法
↓
getAutoConfigurationEntry() // 获取自动配置条目
↓
getCandidateConfigurations() // 🔥 加载候选配置类
↓
┌─────────────────────────────────────────────────┐
│ Spring Boot 2.x: SpringFactoriesLoader │
│ Spring Boot 3.x: ImportCandidates │
└─────────────────────────────────────────────────┘
4.2 入口方法:selectImports()
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 检查自动配置是否开启(默认开启)
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 2. 获取自动配置条目
AutoConfigurationEntry autoConfigurationEntry =
getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
4.3 核心方法:getAutoConfigurationEntry()
protected AutoConfigurationEntry getAutoConfigurationEntry(
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 1. 获取注解属性(exclude 等)
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 2. 🔥 获取候选配置类(这里加载配置文件)
List<String> configurations = getCandidateConfigurations(
annotationMetadata, attributes);
// 3. 去重
configurations = removeDuplicates(configurations);
// 4. 排除指定的配置
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
configurations.removeAll(exclusions);
// 5. 过滤(根据 @Conditional 条件注解)
configurations = getConfigurationClassFilter().filter(configurations);
return new AutoConfigurationEntry(configurations, exclusions);
}
4.4 关键方法:getCandidateConfigurations()
Spring Boot 2.7 及之前版本:
protected List<String> getCandidateConfigurations(
AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 🔥 使用 SpringFactoriesLoader 加载 spring.factories
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), // EnableAutoConfiguration.class
getBeanClassLoader()
);
return configurations;
}
Spring Boot 3.x 版本:
protected List<String> getCandidateConfigurations(
AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 🔥 使用 ImportCandidates 加载新格式的 imports 文件
ImportCandidates candidates = ImportCandidates.load(
AutoConfiguration.class,
getBeanClassLoader()
);
List<String> configurations = new ArrayList<>();
candidates.forEach(configurations::add);
// 兼容:仍然加载 spring.factories(向后兼容)
configurations.addAll(SpringFactoriesLoader.loadFactoryNames(
EnableAutoConfiguration.class,
getBeanClassLoader()
));
return configurations;
}
4.5 底层加载:SpringFactoriesLoader
public final class SpringFactoriesLoader {
// 🔥 固定路径
public static final String FACTORIES_RESOURCE_LOCATION =
"META-INF/spring.factories";
public static List<String> loadFactoryNames(
Class<?> factoryType, ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// 加载所有 spring.factories 文件,按 key 查找
return loadSpringFactories(classLoader)
.getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(
ClassLoader classLoader) {
// 扫描所有 jar 包中的 META-INF/spring.factories
Enumeration<URL> urls = classLoader
.getResources(FACTORIES_RESOURCE_LOCATION);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 解析 Properties 格式
Properties properties = PropertiesLoaderUtils
.loadProperties(new UrlResource(url));
// 按 key-value 解析...
}
}
}
五、为什么 Spring Boot 3 改用新格式?
5.1 两种格式对比
旧格式(spring.factories):
一个 key 后面挂几十甚至上百个配置类,用反斜杠续行:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
# ... 还有几十个,这里省略
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration
痛点:
- 一眼看不出有多少个配置类
- 修改时漏掉逗号或反斜杠就报错
- Git diff 时改一个类,整行都变红
新格式(AutoConfiguration.imports):
每行一个类名,一目了然:
# 每行一个类名,简洁明了
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
优势:
- 有多少个配置类一目了然
- 改一个类只影响一行
- Git diff 清晰易读
5.2 解析过程对比
Properties 格式的解析开销:
读取整个文件为 Properties 对象
↓
解析 Key-Value
↓
处理转义字符(\ 换行续行)
↓
按逗号分割 Value
↓
去除每个值的空格
↓
从 Map 中按 Key 查找
↓
得到配置类列表
每行一个类名的解析开销:
按行读取文件
↓
直接得到类名(跳过空行和注释)
↓
得到配置类列表
5.3 性能差异分析
| 方面 | Properties 格式 | 每行一个类名 |
|---|---|---|
| 解析复杂度 | 需要 Properties 解析器处理转义、续行、等号 | 只需要 readLine() |
| 字符串操作 | 需要按逗号 split + trim | 只需要 trim |
| 内存操作 | 先构建 Map,再按 Key 查找 | 直接追加到 List |
| 代码量 | ~100 行 | ~20 行 |
直观类比:
想象你要从名单中找人:
-
Properties 格式 = 从一本厚厚的电话簿中,先翻到"自动配置"这一页,再把这一页的 100 个名字按逗号分开,逐个抄下来
-
每行一个类名 = 直接打开一个名单,每行就是一个名字,直接复制就行
5.4 Spring Boot 3 改版原因
- 性能优化:新格式解析更快,启动时间略有提升
- 职责分离:
spring.factories承载太多功能(自动配置、Initializer、Listener 等),现在自动配置独立出来 - 更清晰:一个文件只放一种类型的配置,职责单一
- 向后兼容:Spring Boot 3 仍然支持读取
spring.factories,给生态迁移时间
六、条件注解:自动配置的"开关"
不是所有配置都会生效!Spring Boot 用条件注解来智能判断:
| 注解 | 作用 |
|---|---|
@ConditionalOnClass |
classpath 中存在某个类时生效 |
@ConditionalOnMissingBean |
容器中没有某个 Bean 时生效 |
@ConditionalOnProperty |
配置属性满足条件时生效 |
@ConditionalOnWebApplication |
是 Web 应用时生效 |
举个例子:DataSourceAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({...})
public class DataSourceAutoConfiguration {
// 只有当 classpath 里有 DataSource 类,
// 且没有配置 R2DBC 连接工厂时,
// 这个自动配置才会生效
}
这就是为什么你引入 mysql-connector-java 依赖后,数据源自动就配好了!
七、启动流程完整链路
把整个过程串起来:
main() 调用 SpringApplication.run()
↓
创建 SpringApplication 对象
├─ 推断应用类型(Servlet/Reactive)
├─ 加载 ApplicationContextInitializer(从 spring.factories)
└─ 加载 ApplicationListener(从 spring.factories)
↓
执行 run() 方法
├─ 准备环境(Environment)
├─ 打印 Banner
└─ 创建 ApplicationContext
↓
执行 @EnableAutoConfiguration
↓
AutoConfigurationImportSelector.selectImports()
↓
getAutoConfigurationEntry()
↓
getCandidateConfigurations()
├─ Spring Boot 2.x: SpringFactoriesLoader → spring.factories
└─ Spring Boot 3.x: ImportCandidates → AutoConfiguration.imports
↓
加载所有候选自动配置类(100+个)
↓
getConfigurationClassFilter().filter()
└─ 根据 @Conditional 条件过滤
↓
注册生效的 Bean 到容器
↓
启动完成!
八、实战:如何自定义自动配置?
理解原理后,你也可以为自己的组件写自动配置:
Step 1:创建配置类
@Configuration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties props) {
return new MyService(props);
}
}
Step 2:注册到 spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.MyAutoConfiguration
Step 3:打包成 Starter
别人只需引入你的 starter 依赖,配置就自动生效了!
九、总结
Spring Boot 自动配置的完整链路:
1. 入口触发
@SpringBootApplication包含@EnableAutoConfiguration@Import(AutoConfigurationImportSelector.class)导入选择器
2. 配置加载
- Spring Boot 2.x:
SpringFactoriesLoader读取spring.factories - Spring Boot 3.x:
ImportCandidates读取AutoConfiguration.imports
3. 条件过滤
@ConditionalOnClass:类路径存在则生效@ConditionalOnMissingBean:容器中没有则生效@ConditionalOnProperty:配置满足则生效
4. 核心优势
- 约定优于配置:开箱即用,减少样板代码
- 按需加载:条件注解确保只加载需要的配置
- 易于扩展:自定义 Starter 只需遵循规范
掌握了这套机制,你不仅能更好地理解 Spring Boot,还能在面试中从容应对,甚至可以自己写一个优雅的 Starter!
参考资料:
https://javaguide.cn/system-design/framework/spring/spring-boot-auto-assembly-principles.html
https://blog.csdn.net/zuiyuelong/article/details/150500212
https://zhuanlan.zhihu.com/p/301063931
欢迎关注公众号 FishTech Notes,一块交流使用心得!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)