SpringBoot 自动配置是如何实现的?

#Java#面试八股

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

二、常用条件注解

注解判定依据典型用途
@ConditionalOnClassclasspath 是否存在指定类引了 Redis 客户端才装配 RedisTemplate
@ConditionalOnMissingBean容器中是否已有该类型 Bean允许用户自定义覆盖默认实现
@ConditionalOnProperty配置项是否存在/等于某值xxx.enabled=true 才生效
@ConditionalOnWebApplication是否为 Web 应用只在 Servlet 环境装配 Filter
@ConditionalOnSingleCandidate某类型是否只有一个候选多数据源时避免误装配
@ConditionalOnExpressionSpEL 表达式复杂组合条件

三、写一个自己的 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

四、三个必知细节

  1. 顺序问题:@ConditionalOnMissingBean 只有在自动配置类晚于用户配置类时才可靠。Boot 通过 @AutoConfigureAfter/Before 与"用户配置先注册"的策略保证,自己写 starter 时要显式声明顺序。
  2. 条件评估有开销:条件过滤在启动期执行,候选类多时会拖慢启动;可用 --debug 或 ConditionEvaluationReport 查看"为什么没生效"。
  3. 排除方式:@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 的展开。

笔记加载中…