Spring Boot 自动配置的顺序怎么控制?为什么「用户自己定义的 Bean」能覆盖自动配置?
简化版
Spring Boot 自动配置(auto-configuration)之间是有「顺序」和「让位」规则的,核心保证两件事:① 自动配置之间的先后顺序正确;② 用户自定义的 Bean 永远优先于自动配置。控制顺序的注解:@AutoConfigureBefore(我要在某个自动配置之前)、@AutoConfigureAfter(我要在某个之后)、@AutoConfigureOrder(用数字定优先级)——比如 DataSourceAutoConfiguration 要先于 MybatisAutoConfiguration(先有数据源,才能配 MyBatis)。为什么用户 Bean 能覆盖自动配置:因为自动配置里的 Bean 几乎都标了 @ConditionalOnMissingBean——意思是「只有当容器里还没有这种类型的 Bean 时,我才创建」。所以你自己 @Bean 定义了一个,容器里就有了,自动配置的那个因为条件不满足而不创建,你的就生效了。这就是 Spring Boot「约定优于配置、但用户可覆盖」的实现机制:默认帮你配好(自动配置),但你想自定义就自定义(@ConditionalOnMissingBean 让位)。
详细版
顺序控制注解:
| 注解 | 作用 |
|---|---|
@AutoConfigureBefore(X.class) | 本自动配置在 X 之前生效 |
@AutoConfigureAfter(X.class) | 本自动配置在 X 之后生效 |
@AutoConfigureOrder(n) | 用数字定优先级(越小越先) |
关键条件注解(决定「让位」):
| 注解 | 含义 |
|---|---|
@ConditionalOnMissingBean | 容器没有该 Bean 时才创建(让用户覆盖) |
@ConditionalOnBean | 容器有某 Bean 时才创建(依赖它) |
@ConditionalOnClass | classpath 有某类时才生效 |
@ConditionalOnProperty | 某配置项满足时才生效 |
// 简化的自动配置示例(Spring Boot 内部就是这么写的)
@AutoConfiguration
@ConditionalOnClass(DataSource.class) // classpath 有 DataSource 才配
@AutoConfigureAfter(SomeOtherAutoConfiguration.class) // 在它之后
public class DataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean // ★ 用户没定义 DataSource 才创建
public DataSource dataSource() {
return createDefaultDataSource();
}
}
// 用户自定义 → 覆盖自动配置的默认 DataSource
@Configuration
public class MyConfig {
@Bean
public DataSource dataSource() { // 容器里有了,自动配置就不创建了
return myCustomDataSource();
}
}
⚠️ 搞清「自动配置的顺序」和「普通 @Configuration 的顺序」是两套东西:普通的
@Configuration/@Component是「用户配置」,在容器 refresh 时正常处理;而「自动配置」是 Spring Boot 通过spring.factories(2.7 前)或META-INF/spring/...AutoConfiguration.imports(2.7+)加载的一批特殊配置类,它们统一在用户配置之后处理——这保证了「用户 Bean 先注册、自动配置后看到容器里已有用户 Bean、于是@ConditionalOnMissingBean条件不满足而让位」。@AutoConfigureBefore/After/Order只在自动配置之间排序,不影响「用户配置 vs 自动配置」的整体先后(用户永远优先)。理解这个「用户配置先、自动配置后 +@ConditionalOnMissingBean让位」的组合,就理解了 Spring Boot「可覆盖的默认」是怎么实现的。
完整版教学
一、为什么自动配置需要顺序
先理解「自动配置之间为什么要讲顺序」:
自动配置之间常有依赖关系:
DataSourceAutoConfiguration(配数据源)
必须先于
MybatisAutoConfiguration(配 MyBatis,它需要 DataSource)
→ 如果 MyBatis 先配,此时还没有 DataSource,配不出来
再如:
配置 A 产生的 Bean,是配置 B 的前提
→ A 必须在 B 之前
所以自动配置不能"乱序"处理,要有先后:
谁提供基础设施(数据源、连接工厂)→ 先
谁依赖这些基础设施 → 后
Spring Boot 用 @AutoConfigureBefore/After/Order 表达这种先后
自动配置需要顺序,是因为「自动配置之间有依赖关系」——如 DataSourceAutoConfiguration(配数据源)必须先于 MybatisAutoConfiguration(MyBatis 需要数据源),否则 MyBatis 配置时还没有数据源。所以「提供基础设施的先、依赖它的后」。Spring Boot 用 @AutoConfigureBefore/After/Order 表达这种先后。理解「自动配置之间有依赖(数据源先于 MyBatis)、提供基础设施的先依赖的后、需要顺序控制」,就理解了为什么要排序。
二、三个顺序注解
控制自动配置顺序的三个注解:
@AutoConfigureBefore(X.class):
"我要在 X 之前生效"
例:某个自动配置要在 DataSourceAutoConfiguration 之前
@AutoConfigureAfter(X.class):
"我要在 X 之后生效"
例:MybatisAutoConfiguration 标 @AutoConfigureAfter(DataSourceAutoConfiguration)
→ 保证先有数据源
@AutoConfigureOrder(n):
用数字定优先级(类似 @Order),值越小越先
用于没有明确 before/after 关系、但想定个大致优先级
注意:这些只影响"自动配置之间"的相对顺序
不是精确的执行顺序控制(自动配置本质是配置类,
真正 Bean 的创建顺序还受依赖关系影响)
它们主要影响"自动配置类被处理/解析的先后"
三个顺序注解:@AutoConfigureBefore(X)(在 X 之前)、@AutoConfigureAfter(X)(在 X 之后,如 MyBatis 标 @AutoConfigureAfter(DataSourceAutoConfiguration) 保证先有数据源)、@AutoConfigureOrder(n)(数字定优先级,越小越先)。它们只影响自动配置之间的相对顺序(自动配置类被处理的先后),不是精确的 Bean 创建顺序(那还受依赖影响)。理解「@AutoConfigureBefore/After 定相对先后(MyBatis After DataSource)、@AutoConfigureOrder 数字定优先级、只影响自动配置间顺序」,就掌握了三个顺序注解。
三、核心:@ConditionalOnMissingBean 让位机制
Spring Boot「用户可覆盖默认」的核心是 @ConditionalOnMissingBean:
自动配置里的 Bean 几乎都标了 @ConditionalOnMissingBean:
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() { ... }
含义:"只有当容器里还没有 DataSource 类型的 Bean 时,我才创建这个"
所以:
情况一:用户没定义 DataSource
→ 容器里没有 → 条件满足 → 自动配置创建默认的 DataSource
情况二:用户自己 @Bean 定义了 DataSource
→ 容器里已经有了 → @ConditionalOnMissingBean 条件不满足
→ 自动配置的默认 DataSource 不创建 → 用户的生效!
这就是"约定优于配置、但可覆盖":
你不管 → Spring Boot 给你配一个默认的(自动配置)
你想自己定 → 你定的优先,默认的自动让位
@ConditionalOnMissingBean 是「让位机制」的核心——自动配置里的 Bean 几乎都标了它,意思是「容器里没有这种 Bean 时我才创建」。所以用户自己定义了,容器里就有了,自动配置的那个因条件不满足而不创建,用户的生效。这就是「约定优于配置、但可覆盖」:不管就用默认,想自定义就覆盖。理解「@ConditionalOnMissingBean=容器没有该 Bean 才创建、用户定义了自动配置就让位、实现’可覆盖的默认’」,就抓住了 Spring Boot 自动配置最精妙的机制。
四、为什么用户配置能「先被看到」
@ConditionalOnMissingBean 能让位的前提是「用户配置先注册、自动配置后处理」:
关键问题:@ConditionalOnMissingBean 判断"容器有没有该 Bean"
→ 那必须保证"用户的 Bean 先注册进去",自动配置才能"看到"它
Spring Boot 的保证:自动配置统一在"用户配置之后"处理
处理顺序:
1. 先处理用户的 @Configuration/@Component(用户的 Bean 定义先注册)
2. 再处理自动配置类(通过 @EnableAutoConfiguration 导入的那批)
→ 自动配置处理时,用户的 Bean 定义已经在容器里了
→ @ConditionalOnMissingBean 一看"已经有用户的了" → 让位
实现:自动配置是通过 @Import(AutoConfigurationImportSelector) 导入的
这个 ImportSelector 是"延迟"的(DeferredImportSelector)
→ 它导入的配置类被"最后处理"
→ 所以自动配置总是排在用户配置后面
★ 这就是为什么用户永远能覆盖自动配置:
用户先注册 + 自动配置后看到 + @ConditionalOnMissingBean 让位
@ConditionalOnMissingBean 能让位的前提是「用户配置先处理、自动配置后处理」——这样自动配置处理时能「看到」用户已注册的 Bean。Spring Boot 通过 @Import(AutoConfigurationImportSelector) 导入自动配置,它是 DeferredImportSelector(延迟的),导入的配置类最后处理,所以自动配置总排在用户配置后面。用户先注册 + 自动配置后看到 + @ConditionalOnMissingBean 让位 = 用户永远能覆盖自动配置。理解「自动配置靠 DeferredImportSelector 延迟导入、排在用户配置之后、所以能看到用户 Bean 并让位」,就理解了「用户永远优先」的实现原理。
五、其他条件注解:精细控制生效
除了 @ConditionalOnMissingBean,还有一族条件注解精细控制自动配置何时生效:
@ConditionalOnClass(X.class):
classpath 上有 X 类才生效
例:DataSourceAutoConfiguration @ConditionalOnClass(DataSource.class)
→ 没引入相关依赖(classpath 没这个类)就不配(不多管闲事)
@ConditionalOnMissingBean:容器没该 Bean 才创建(让用户覆盖)
@ConditionalOnBean:容器有某 Bean 才创建(依赖它存在)
@ConditionalOnProperty(name="x.enabled", havingValue="true"):
某配置项满足才生效
→ 用配置开关控制某自动配置开不开
@ConditionalOnWebApplication / @ConditionalOnNotWebApplication:
是不是 web 应用
组合使用:
一个自动配置类,往往同时标多个条件:
"classpath 有这个库(@ConditionalOnClass) +
用户没自己配(@ConditionalOnMissingBean) +
配置开关打开(@ConditionalOnProperty) → 才生效"
→ 非常精细地判断"到底要不要自动配置"
自动配置的生效由一族条件注解精细控制:@ConditionalOnClass(classpath 有某类才配,没引入依赖就不管闲事)、@ConditionalOnMissingBean(用户没配才配,让位)、@ConditionalOnBean(依赖某 Bean 存在)、@ConditionalOnProperty(配置开关控制)、@ConditionalOnWebApplication(是否 web)。它们组合使用精细判断「到底要不要自动配置」。这就是自动配置「智能」的来源——按 classpath、已有 Bean、配置开关综合判断。理解「条件注解族:@ConditionalOnClass(有类才配)/@ConditionalOnMissingBean(让位)/@ConditionalOnProperty(开关)、组合使用精细判断是否生效」,就理解了自动配置的条件控制。
六、排查自动配置:为什么生效/不生效
实际中常需排查「某个自动配置为什么生效/不生效」,Spring Boot 提供了工具:
排查手段:
① 启动加 --debug:打印"自动配置报告(Condition Evaluation Report)"
- Positive matches:哪些自动配置生效了、因为满足了什么条件
- Negative matches:哪些没生效、因为哪个条件不满足
(如"@ConditionalOnClass 没找到 X 类"、
"@ConditionalOnMissingBean 因为已有用户的 Bean 而没生效")
② Actuator 的 /conditions 端点:同样的条件评估报告
常见问题诊断:
"我配的 Bean 没生效,用的还是默认的"
→ 可能你的 Bean 注册太晚 / 条件没满足 / 根本没被扫描到
"自动配置没生效"
→ 看 Negative matches:多半是 @ConditionalOnClass 缺依赖、
或 @ConditionalOnProperty 开关没开
"两个自动配置顺序不对导致报错"
→ 检查 @AutoConfigureBefore/After 关系
排查思路:先看 Condition 报告,定位是哪个条件挡住了
排查自动配置用 --debug 启动参数(打印「自动配置报告」:Positive matches 生效的、Negative matches 没生效的及原因)或 Actuator 的 /conditions 端点。常见诊断:「我的 Bean 没生效」(可能条件没满足/没被扫描)、「自动配置没生效」(看 Negative matches,多半是缺依赖或开关没开)。排查思路是「先看 Condition 报告,定位哪个条件挡住了」。理解「用 —debug 或 /conditions 看自动配置报告(Positive/Negative matches 及原因)、排查从 Condition 报告定位哪个条件挡住」,就掌握了自动配置的排查方法。
记忆钩子:「Spring Boot 自动配置顺序与覆盖:①顺序控制 @AutoConfigureBefore/After(如 MyBatis After DataSource,提供基础设施的先)/@AutoConfigureOrder(数字);②用户 Bean 覆盖自动配置的核心=@ConditionalOnMissingBean(容器没该 Bean 才创建,用户定义了就让位);③能让位的前提:自动配置靠 DeferredImportSelector 延迟导入、排在用户配置之后、能看到用户 Bean;条件注解族:@ConditionalOnClass(有类才配)/@ConditionalOnMissingBean(让位)/@ConditionalOnProperty(开关)组合精细判断;排查用 —debug 或 /conditions 看 Positive/Negative matches」。
七、常见误区与追问
- 误区:自动配置会覆盖用户自己定义的 Bean。 恰恰相反——用户 Bean 永远优先;自动配置的 Bean 几乎都标 @ConditionalOnMissingBean(容器没有才创建),用户定义了同类型 Bean,自动配置的就不创建、让位给用户。
- 误区:@AutoConfigureBefore/After 能控制「用户配置 vs 自动配置」的顺序。 它们只在「自动配置之间」排序;用户配置永远先于自动配置处理(自动配置靠 DeferredImportSelector 延迟导入排在最后),这个整体先后不受这些注解影响。
- 误区:只要引入了 starter,自动配置就一定生效。 还要满足各种条件——@ConditionalOnClass(classpath 有相关类)、@ConditionalOnProperty(配置开关)、@ConditionalOnMissingBean(用户没自己配)等;任一条件不满足就不生效,可用 —debug 看 Negative matches 定位。
- 误区:自动配置和普通 @Configuration 处理方式一样。 自动配置是通过 spring.factories(2.7前)/AutoConfiguration.imports(2.7+) 加载、用 DeferredImportSelector 延迟导入的特殊配置类,统一排在用户配置之后处理;普通 @Configuration 是用户配置,正常在 refresh 时处理、先于自动配置。
- 追问:为什么用户自定义的 Bean 总能覆盖自动配置? 三个机制配合:① 用户配置先处理、Bean 先注册;② 自动配置靠 DeferredImportSelector 延迟导入、排在用户配置之后处理,能「看到」容器里已有用户 Bean;③ 自动配置的 Bean 标 @ConditionalOnMissingBean,发现已有用户 Bean 就让位不创建。
- 追问:怎么让一个自动配置在另一个之后生效? 用 @AutoConfigureAfter(XxxAutoConfiguration.class),如 MybatisAutoConfiguration 标 @AutoConfigureAfter(DataSourceAutoConfiguration.class),保证数据源先配好、MyBatis 才能拿到它;反向用 @AutoConfigureBefore,无明确依赖但想定优先级用 @AutoConfigureOrder。
- 追问:怎么排查一个自动配置为什么没生效? 启动加 —debug 参数(或访问 Actuator 的 /conditions 端点)看条件评估报告的 Negative matches——会告诉你是哪个条件不满足(如 @ConditionalOnClass 没找到某类=缺依赖、@ConditionalOnProperty 开关没开、@ConditionalOnMissingBean 因已有用户 Bean 而没生效)。
八、加强记忆
Spring Boot 自动配置有「顺序」和「让位」规则,保证:① 自动配置之间先后正确;② 用户自定义 Bean 永远优先。顺序控制:@AutoConfigureBefore(X)(在 X 之前)、@AutoConfigureAfter(X)(在 X 之后,如 MybatisAutoConfiguration 标 @AutoConfigureAfter(DataSourceAutoConfiguration) 保证先有数据源)、@AutoConfigureOrder(n)(数字定优先级)——只影响自动配置之间的相对顺序。用户 Bean 能覆盖自动配置的核心是 @ConditionalOnMissingBean——自动配置的 Bean 几乎都标它,意为「容器没有该 Bean 才创建」,用户定义了同类型 Bean、自动配置就让位不创建。能让位的前提:自动配置靠 DeferredImportSelector(延迟导入) 排在用户配置之后处理,能「看到」容器里已有的用户 Bean——用户先注册 + 自动配置后看到 + @ConditionalOnMissingBean 让位 = 用户永远优先。条件注解族精细控制生效:@ConditionalOnClass(有类才配)、@ConditionalOnMissingBean(让位)、@ConditionalOnBean(依赖存在)、@ConditionalOnProperty(配置开关)、@ConditionalOnWebApplication——组合判断「要不要自动配置」。排查用 --debug 或 Actuator /conditions 看条件报告(Positive/Negative matches 及原因)。这就是「约定优于配置、但可覆盖」的实现。一句话「自动配置顺序用 @AutoConfigureBefore/After/Order(只排自动配置间);用户 Bean 覆盖靠 @ConditionalOnMissingBean(容器没有才创建);能让位因自动配置靠 DeferredImportSelector 延迟导入排在用户配置后、能看到用户 Bean;条件注解族(@ConditionalOnClass/Property 等)控制生效;—debug 看条件报告排查」。