Spring Boot 自动配置原理详解
定位:02 篇,讲透自动配置的完整触发链:从 @EnableAutoConfiguration 到候选清单加载、条件过滤、用户优先机制,以及调试排查手段
适用版本:Spring Boot 3.x(JDK 17+)
前置:条件装配注解族见 Framework-05篇
目录
一、触发链
自动配置由 @EnableAutoConfiguration(含在 @SpringBootApplication 内)触发,全链路:
@SpringBootApplication
└── @EnableAutoConfiguration
└── @Import(AutoConfigurationImportSelector.class) ← 关键
└── selectImports():
① 读取候选自动配置类清单
② 去重、按排除项过滤(exclude)
③ 返回全限定名数组 → 注册为配置类
核心角色 AutoConfigurationImportSelector:它实现 DeferredImportSelector(延迟导入),在用户配置类处理完之后才导入自动配置------这是"用户优先"的顺序基础。
二、候选清单加载
2.1 清单位置(版本差异,高频考点)
| 版本 | 清单文件 |
|---|---|
| 2.7 及以前 | META-INF/spring.factories(EnableAutoConfiguration 键) |
| 3.x | META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(每行一个类名) |
各依赖的 jar(如 spring-boot-autoconfigure、各 starter 的配套包)都在自己的该文件里声明自己的自动配置类;Selector 聚合所有 jar 的清单。
2.2 候选数量级
一个典型 Web 应用聚合到的候选有上百个,但绝大多数因条件不满足被跳过------"加载清单"不等于"全部装配"。
三、条件过滤
3.1 自动配置类 = 配置类 + 条件
java
@AutoConfiguration
@ConditionalOnClass(DataSource.class) // classpath 有相关类才考虑
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 用户没定义才注册默认
public DataSource dataSource(...) { ... }
}
条件注解回顾(FW-05):
| 注解 | 判断 |
|---|---|
| @ConditionalOnClass / OnMissingClass | classpath 存在/缺少类 |
| @ConditionalOnProperty | 配置属性开关 |
| @ConditionalOnBean / OnMissingBean | 容器有/无某 Bean |
3.2 评估时机
条件在 Bean 定义注册阶段评估。自动配置类被注册为配置类后,其 @Conditional 决定是否真正生效------不满足则整个类(含其中 @Bean)跳过。
3.3 三层过滤漏斗
上百个候选类
↓ @ConditionalOnClass(有依赖才进门)
几十个
↓ @ConditionalOnProperty / OnWebApplication 等
更少
↓ @ConditionalOnMissingBean(用户没定义才装)
最终实际生效的自动配置
四、用户优先机制
"约定大于配置"必须保证用户定义永远能覆盖默认,靠两点:
-
@ConditionalOnMissingBean:自动配置注册默认 Bean 前检查"容器是否已有",有则让位;
-
处理顺序 :
AutoConfigurationImportSelector是 DeferredImportSelector,用户配置类先处理,自动配置后处理------所以 OnMissingBean 检查时用户的定义已就绪。用户:@Bean DataSource 自定义 → 先注册
自动配置:OnMissingBean(DataSource) → 发现已有 → 跳过
结果:用户的生效,不冲突
若顺序反了(自动配置先处理),OnMissingBean 看不到用户定义,就会冲突或覆盖------这就是自定义配置类间乱用 OnBean 会误判的原因(FW-05)。
五、常见自动配置举例
| 自动配置类 | 装什么 |
|---|---|
WebMvcAutoConfiguration |
DispatcherServlet、内嵌容器、静态资源 |
DataSourceAutoConfiguration |
数据源(需配置 url 或内嵌库) |
RedisAutoConfiguration |
RedisTemplate/StringRedisTemplate |
JacksonAutoConfiguration |
ObjectMapper(JSON 转换) |
TaskExecutionAutoConfiguration |
默认线程池(@Async 兜底) |
理解套路后,看任何"starter 引入了就自动能用"的现象,都能定位到对应的自动配置类。
六、调试与排查
"为什么没生效/为什么这样配"的标准手段:
| 手段 | 用法 |
|---|---|
| 条件报告 | 启动加 --debug(或 debug: true),打印 ConditionEvaluationReport:列出每个自动配置的匹配/不匹配及原因 |
| Actuator | /actuator/conditions 运行时查看 |
| 排除项 | @SpringBootApplication(exclude = XxxAutoConfiguration.class) 或 spring.autoconfigure.exclude |
排查"某 Bean 没出现"的顺序:
① 相关依赖(starter)是否引入 → OnClass 进门条件
② 是否需要配置属性(如 datasource 需要 url)→ OnProperty
③ 是否被自己误定义/误排除 → OnMissingBean/exclude
④ 看 --debug 报告中该配置类的具体判定
七、总结
- 触发链:@SpringBootApplication → @EnableAutoConfiguration → @Import(AutoConfigurationImportSelector) → 读清单、过滤、注册。
- 候选清单:2.7 前在 spring.factories,3.x 在 AutoConfiguration.imports;聚合所有 jar 的声明,候选上百但按需装配。
- 条件过滤漏斗:OnClass(有依赖)→ OnProperty 等 → OnMissingBean(用户没定义),层层收敛到实际生效集。
- 用户优先:DeferredImportSelector 保证用户配置先处理 + OnMissingBean 让位,实现"默认生效又处处可覆盖"。
- 排查:--debug 的 ConditionEvaluationReport 是自动配置的"透视镜",配合 exclude 精确控制。
八、常见高频面试题
1. 详细说说 Spring Boot 自动配置的原理。
要点:链路上,@SpringBootApplication 的 @EnableAutoConfiguration 通过 @Import 引入 AutoConfigurationImportSelector;它读取所有 jar 中的自动配置类清单(3.x 为 META-INF/spring/...AutoConfiguration.imports),聚合去重后作为候选;每个自动配置类带 @Conditional 条件(OnClass/OnProperty/OnMissingBean 等),在注册阶段按条件过滤,只有满足的才真正装配默认 Bean。本质是"大量预写配置类 + 条件化按需生效"。
2. spring.factories 和 AutoConfiguration.imports 的区别?
要点:都是声明自动配置类清单的文件,是版本演进关系。2.7 及以前用 META-INF/spring.factories(键值对,EnableAutoConfiguration=类名列表,还承载其它 SPI 式键);3.x 改用专门的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(每行一个类名,语义单一)。迁移时旧文件不再被读取,是 Boot 2→3 升级的常见坑。
3. 自动配置怎么保证不和用户自定义的 Bean 冲突?
要点:两机制配合。① @ConditionalOnMissingBean------自动配置注册默认 Bean 前检查容器中是否已有该类型/名称的 Bean,有则跳过让位;② 顺序保证------AutoConfigurationImportSelector 是 DeferredImportSelector,用户配置类先处理、自动配置后处理,所以检查时用户定义已就绪。从而实现"约定大于配置":默认生效,用户一旦自定义即覆盖,无冲突。
4. @EnableAutoConfiguration 是如何工作的?
要点:它通过 @Import 导入 AutoConfigurationImportSelector,后者实现 DeferredImportSelector,在配置类处理后期执行 selectImports():读取候选清单、去除重复与显式排除项(@EnableAutoConfiguration(exclude=...) 或 spring.autoconfigure.exclude)、返回要导入的自动配置类名。这些类随后按各自 @Conditional 条件决定是否生效。它是连接"注解开关"与"候选加载"的桥梁。
5. 怎么排除某个自动配置?
要点:三种方式。① 注解:@SpringBootApplication(exclude = XxxAutoConfiguration.class) 或 excludeName 按类名;② 配置:spring.autoconfigure.exclude=全限定名(可多个);③ 精确控制:不排除整个类,而是自己定义同类型 Bean 让 OnMissingBean 让位。常见场景:关闭默认数据源自动配置(用自定义)、关闭安全默认拦截等。
6. 为什么我引入了依赖但某个 Bean 没有自动配置出来?
要点:按条件漏斗排查。① OnClass 是否满足(依赖是否真引入且版本对);② 是否依赖配置属性(如 DataSource 需要 spring.datasource.url,缺了就不装);③ 是否被自己误排除或误定义了同类型 Bean;④ 用 --debug 启动看 ConditionEvaluationReport 中该自动配置类的具体不匹配原因,这是最直接的定位手段。
7. 自动配置类上的 @ConditionalOnClass 在类不存在时不会 ClassNotFoundException 吗?
要点:不会,这正是设计巧妙处。条件评估在解析配置类的元数据阶段进行,通过 ASM 字节码读取注解信息而不真正加载该类;若 @ConditionalOnClass 指定的类不在 classpath,整个自动配置类在条件判定时即被跳过,不会触发类加载,也就没有 ClassNotFoundException。这让"按依赖存在与否装配"安全可行。
8. 自动配置和组件扫描的区别?
要点:两者都往容器注册东西,但来源与目的不同。组件扫描(@ComponentScan)发现用户代码中的 @Component/@Service 等,注册业务 Bean;自动配置来自依赖 jar 的清单文件,按条件装配框架默认组件(数据源、模板、MVC)。扫描是"找到你写的",自动配置是"按依赖给你默认的"。两者在 refresh 阶段都产出 BeanDefinition。
9. 什么是 DeferredImportSelector?为什么自动配置用它?
要点:ImportSelector 的延迟变体,其导入的配置类在所有普通 @Configuration 类(包括用户配置)处理完之后才处理。自动配置用它,是为了保证用户定义先注册------这样自动配置里的 @ConditionalOnMissingBean 才能看到用户 Bean 并正确让位,实现"用户优先、默认兜底"。若用普通 Import 同时处理,顺序无法保证,覆盖语义会出错。
10. 如何自定义一个自动配置(让别人引入你的 jar 就自动生效)?
要点:① 写 @AutoConfiguration(或 @Configuration + @Conditional)配置类,内含 @Bean 定义,配好 @ConditionalOnClass/OnProperty/OnMissingBean 等条件;② 把类全限定名写入 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(3.x);③ 随你的 starter jar 发布。这样使用方引入依赖即自动装配,可用属性开关与自定义 Bean 覆盖------这就是自定义 starter 的核心