SpringBoot 自动配置是如何实现的?
结论先行:自动配置 = 候选清单 + 条件过滤 + 按需注册。@SpringBootApplication 里的 @EnableAutoConfiguration 导入 AutoConfigurationImportSelector,它读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Boot 2.7+,之前是 spring.factories)得到上百个候选配置类,再用 @Conditional 系列注解按 classpath、已有 Bean、配置项、Web 类型逐个筛选,通过的才注册 BeanDefinition。所以"引了依赖就自动有 Bean,没引就自动不生效"。
一、链路拆解
@SpringBootApplication
├─ @SpringBootConfiguration
├─ @ComponentScan
└─ @EnableAutoConfiguration
└─ @Import(AutoConfigurationImportSelector.class)
├─ getCandidateConfigurations()
│ └─ 读 AutoConfiguration.imports / spring.factories 中的候选类名
├─ 去重、排除(exclude / excludeName)
├─ 条件过滤:OnClassCondition、OnBeanCondition、OnWebApplicationCondition
└─ 返回通过筛选的配置类 → 注册其 @Bean 方法为 BeanDefinition
二、常用条件注解
| 注解 | 判定依据 | 典型用途 |
|---|---|---|
@ConditionalOnClass | classpath 是否存在指定类 | 引了 Redis 客户端才装配 RedisTemplate |
@ConditionalOnMissingBean | 容器中是否已有该类型 Bean | 允许用户自定义覆盖默认实现 |
@ConditionalOnProperty | 配置项是否存在/等于某值 | xxx.enabled=true 才生效 |
@ConditionalOnWebApplication | 是否为 Web 应用 | 只在 Servlet 环境装配 Filter |
@ConditionalOnSingleCandidate | 某类型是否只有一个候选 | 多数据源时避免误装配 |
@ConditionalOnExpression | SpEL 表达式 | 复杂组合条件 |
三、写一个自己的 starter
// 1. 自动配置类:全部条件化,缺什么就不装配
@AutoConfiguration
@ConditionalOnClass(SmsClient.class) // 引了 SDK 才生效
@EnableConfigurationProperties(SmsProperties.class) // 绑定 sms.* 配置
public class SmsAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 用户自定义优先
public SmsClient smsClient(SmsProperties props) {
return new SmsClient(props.getEndpoint(), props.getAccessKey());
}
@Bean
@ConditionalOnProperty(prefix = "sms", name = "async", havingValue = "true")
public SmsAsyncSender smsAsyncSender(SmsClient client) {
return new SmsAsyncSender(client);
}
}
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
private String endpoint = "https://sms.example.com";
private String accessKey;
private boolean async = false;
// getter / setter 省略
}
# 2. 注册文件(Boot 2.7+ 及 3.x)
src/main/resources/META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容:
com.demo.sms.SmsAutoConfiguration
# 旧版本(2.6 及以前)用 spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.sms.SmsAutoConfiguration
# 3. 使用方只需引依赖 + 配置
sms:
endpoint: https://sms.internal
access-key: ${SMS_KEY}
async: true
四、三个必知细节
- 顺序问题:
@ConditionalOnMissingBean只有在自动配置类晚于用户配置类时才可靠。Boot 通过@AutoConfigureAfter/Before与"用户配置先注册"的策略保证,自己写 starter 时要显式声明顺序。 - 条件评估有开销:条件过滤在启动期执行,候选类多时会拖慢启动;可用
--debug或ConditionEvaluationReport查看"为什么没生效"。 - 排除方式:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)或spring.autoconfigure.exclude=...。
五、常见追问
- 问:
@ConditionalOnMissingBean能用在@Bean方法上吗? 能,且推荐这样写——同类型 Bean 已存在时跳过,实现"默认值可被覆盖"。 - 问:为什么自动配置类不用
@ComponentScan? 自动配置类由@Import精确导入,扫描会带来不可控的类加载和条件评估,且顺序难以保证。 - 问:
spring.factories完全废弃了吗? 自动配置的注册已迁移到AutoConfiguration.imports;spring.factories仍用于其他扩展点(如ApplicationListener、EnvironmentPostProcessor)。 - 问:自动配置和
@ComponentScan谁先? 扫描先注册用户 Bean,自动配置的条件判断随后执行,因此@ConditionalOnMissingBean能看到用户 Bean。 - 问:怎么排查"明明引了依赖却没生效"? 开
debug=true看条件评估报告,或断点在AutoConfigurationImportSelector#getCandidateConfigurations看候选是否被读到。
小结
自动配置的三板斧:清单(imports 文件)、条件(@Conditional 系列)、顺序(AutoConfigureAfter/用户优先)。会写一个带 @ConditionalOnMissingBean 与 @ConfigurationProperties 的 starter,就说明真理解了,而不只是会背 @SpringBootApplication 的展开。