Spring Boot 自动配置原理

候选人小赵在字节跳动面试,面到 Spring Boot 部分时,面试官问了一个看似常规的问题:

"Spring Boot 的自动配置是怎么实现的?"

小赵张嘴就来:"就是 Spring Boot 会自动扫描 classpath 下的配置类,然后根据条件注解来决定要不要生效。"面试官点点头,追问:"那它是怎么扫描到这些配置类的?扫描的是哪个文件?"

小赵愣了一下:"好像是 spring.factories?"面试官继续:"那 Spring Boot 2.7 和 3.x 版本有什么区别?"小赵彻底卡住了。

【面试官心理】

这道题我通常用来试探候选人对 Spring Boot "约定大于配置"这个核心理念的理解程度。能背出 spring.factories 的占 60%,能说出 AutoConfigurationImportSelector 工作流程的占 30%,能讲清楚 Spring Boot 3.x 变化的只有 10%。这道题能答到最后的,基本都在 Spring Boot 上踩过不少坑。

一、自动配置的核心入口 🔴

1.1 从 @SpringBootApplication 说起

一切要从 @SpringBootApplication 这个注解说起。它的底层组合了三个关键注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationImportFilter.class) })
public @interface SpringBootApplication {
    // ...
}

其中 @EnableAutoConfiguration 才是自动配置的真正入口。这个注解通过 @Import 导入了一个关键类:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String name() default EnablingAutoConfiguration.AUTO_CONFIGURATION_MODULE_NAME;
    // ...
}

1.2 AutoConfigurationImportSelector 的加载流程

AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口,它的核心方法 selectImports() 调用了 getCandidateConfigurations()

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 关键:SpringFactoriesLoader.loadFactoryNames
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
        getSpringFactoriesLoaderFactoryClass(),
        getBeanClassLoader()
    );
    return configurations;
}

这里 SpringFactoriesLoader.loadFactoryNames() 会去 classpath 下寻找 META-INF/spring.factories 文件,读取其中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 键对应的所有自动配置类全限定名。

# META-INF/spring.factories (Spring Boot 2.x)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
  org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
  org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
  ...
⚠️

Spring Boot 2.7 之前,所有自动配置类都在 spring.factoriesEnableAutoConfiguration 键下列出。这个文件可能有几千行,面试时说"我看过 spring.factories"的人,十有八九只是瞟了一眼。

1.3 Spring Boot 2.7+ 的重大变化

2022 年 Spring Boot 2.7 引入了 AutoConfiguration.imports 文件,彻底改变了自动配置的加载方式:

# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports (Spring Boot 2.7+)
# 每个自动配置类一行,不再是键值对
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

Spring Boot 3.x 完全移除了 spring.factories,统一使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

// Spring Boot 3.x 的加载逻辑核心
List<AutoConfigurationEntry> entries = new ArrayList<>();
AutoConfigurationImports.of(this.beanFactory, this.resourceLoader, this.ads);

核心区别

版本配置文件位置文件格式
Spring Boot 2.7 之前META-INF/spring.factoriesEnableAutoConfiguration=xxx,yyy
Spring Boot 2.7META-INF/spring.factories + AutoConfiguration.imports两套并行,优先 imports
Spring Boot 3.xMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports仅 imports
💡

面试时如果能主动提到 Spring Boot 3.x 的变化,面试官会高看你一眼。这意味着你不只是会用,还关注框架演进。

二、条件注解体系 🟡

2.1 @Conditional 系列注解

光扫描到配置类还不够,Spring Boot 还需要根据条件决定这些配置类是否生效。这套机制靠的是 @Conditional 系列注解:

@Conditional(OnBeanCondition.class)
@Conditional(OnClassCondition.class)
@Conditional(OnPropertyCondition.class)
@Conditional(OnWebApplicationCondition.class)

DataSourceAutoConfiguration 为例:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnProperty(name = "spring.datasource.enable", havingValue = "true", matchIfMissing = true)
@ConditionalOnMissingBean(DataSource.class)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
    // ...
}

2.2 配置类的执行顺序

如果多个自动配置类有依赖关系,Spring Boot 提供了控制顺序的注解:

@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
📖 点击展开 Spring Boot 自动配置完整流程图
flowchart TD
    A[SpringApplication.run] --> B[@EnableAutoConfiguration]
    B --> C[@Import AutoConfigurationImportSelector]
    C --> D[getCandidateConfigurations]
    D --> E[SpringFactoriesLoader.loadFactoryNames]
    E --> F[扫描 spring.factories 或 AutoConfiguration.imports]
    F --> G[获取所有自动配置类全限定名]
    G --> H[按 @AutoConfigureOrder 排序]
    H --> I[逐个实例化配置类]
    I --> J[@Conditional 条件过滤]
    J --> K{条件是否满足?}
    K -->|是| L[注册 Bean]
    K -->|否| M[跳过该配置类]

三、❌ 错误示范

3.1 背题型翻车

候选人原话:"Spring Boot 的自动配置就是通过 spring.factories 文件实现的,会自动扫描配置类然后注册 bean。"

问题诊断

  • 只知道 spring.factories,不知道 AutoConfiguration.imports 的存在
  • 把"加载"和"生效"混为一谈——加载不等于生效,还需要 @Conditional 判断
  • 完全不知道 Spring Boot 3.x 的变化

面试官内心 OS:"这个候选人只是在背题,没实际研究过源码。Spring Boot 3.x 都发布两年了,还在死磕 spring.factories。"

3.2 混淆概念型

候选人原话:"@ComponentScan 和 @EnableAutoConfiguration 是一样的,都会扫描 Bean。"

问题诊断

  • 完全不理解两个注解的本质区别:@ComponentScan 扫描的是加了 @Component 及其派生注解的类,而 @EnableAutoConfiguration 扫描的是 spring.factories/AutoConfiguration.imports 中声明的 @Configuration
  • 不知道 @ComponentScan 默认扫描当前包及子包

3.3 知其然不知其所以然

候选人原话:"@ConditionalOnClass 就是检查类存不存在,存在就加载。"

问题诊断

  • 知道有条件判断,但不知道 @ConditionalOnClass 的底层实现原理——它是通过 ASM 动态检查字节码,而不是真正加载类
  • 不知道"检查类存在"是为了避免 ClassNotFoundException 的连锁反应

【面试官心理】

这三种错误我在面试中见过太多次。第一种是纯背书,第二种是概念混乱,第三种是只知表面。我通常会在候选人说完后追问三个问题:1. "那自动配置类的加载顺序怎么控制?" 2. "Spring Boot 3.x 改了什么?" 3. "@ConditionalOnMissingBean 和 @ConditionalOnBean 有什么区别?"能答出两个以上的,基本都有实战经验。

四、标准回答 🟢

4.1 P5 级别:能说清楚基本流程

"Spring Boot 的自动配置核心靠 @EnableAutoConfiguration 注解触发,它通过 @Import 导入了 AutoConfigurationImportSelector。这个类会在启动时调用 SpringFactoriesLoader.loadFactoryNames(),去 classpath 下的 META-INF/spring.factories 文件中读取 EnableAutoConfiguration 键对应的所有自动配置类。然后根据 @Conditional 系列条件注解过滤,最终注册符合条件的配置类到容器中。"

4.2 P6 级别:能说出版本差异和源码细节

"自动配置的核心链路是:SpringApplication.run@EnableAutoConfigurationAutoConfigurationImportSelector.getCandidateConfigurations()SpringFactoriesLoader.loadFactoryNames()

Spring Boot 2.7 之前使用 META-INF/spring.factories,格式是键值对;2.7 引入了 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,每个配置类占一行;3.x 完全移除了 spring.factories。

配置类加载后不会立即生效,而是经过 @Conditional 系列条件注解的过滤——比如 @ConditionalOnClass 检查字节码中是否存在目标类,@ConditionalOnProperty 检查配置属性,@ConditionalOnMissingBean 检查容器中是否已存在该 Bean。执行顺序通过 @AutoConfigureBefore/After/Order 控制。"

4.3 P7 级别:能讲清楚设计意图和生产实践

"这道题背后其实考的是 Spring Boot '约定大于配置' 的核心理念。自动配置的本质是:框架作者预先写好了一堆配置类,用户只需要引入 starter 依赖、修改少量配置,就能开箱即用。

我之前在项目里排查过一个坑:项目引入了 Redis starter,但由于我们自定义了 RedisTemplate,导致自动配置的 RedisAutoConfiguration 被跳过了。后来定位到是 @ConditionalOnMissingBean 在起作用——如果用户已经注册了某个类型的 Bean,框架就不会再自动配置它。

Spring Boot 3.x 的变化也值得关注:从 spring.factories 迁移到 imports 文件,本质上是把扫描范围从整个 classpath 收敛到 starter 自己的 jar 包内,避免类和配置的相互污染。同时 3.x 引入了 @AutoConfiguration 注解替代普通 @Configuration,进一步区分了自动配置类和用户自定义配置类。"

五、生产避坑

5.1 踩过的真实坑

场景:我们在升级 Spring Boot 2.4 到 2.7 时,发现某个自定义的自动配置类突然不生效了。排查了半天,发现是 Spring Boot 2.7 改了 spring.factories 的加载优先级——AutoConfiguration.imports 文件中的配置优先于 spring.factories。

根因:spring.factories 是全局扫描,而 AutoConfiguration.imports 是每个 starter 自己管理。我们自定义 starter 的 spring.factories 被 Spring Boot 自己的 spring-boot-autoconfigure 包的配置覆盖了。

解法:把配置迁移到 imports 文件,或者使用 @AutoConfiguration 注解。

5.2 排查工具

# 打印所有自动配置类及其生效状态
java -jar app.jar --debug

# 或者在 application.yml 中开启
debug: true
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

开启 debug 模式后,Spring Boot 会输出一个 AutoConfiguration report,显示哪些自动配置类通过了条件检查,哪些被跳过了,跳过的原因是什么。

💡

生产环境强烈建议开启 debug 模式排查一次,把生效的自动配置都过一遍。这样你才能知道项目到底依赖了哪些框架特性,避免引入无用的 starter 增大包体积。

六、工程选型

场景推荐方案说明
新项目Spring Boot 3.x直接用 AutoConfiguration.imports
旧项目升级迁移到 imports 文件2.7 开始建议逐步迁移
自定义 starter必须用 imports 文件避免和框架的 spring.factories 冲突
禁用某个自动配置@SpringBootApplication(exclude=...)spring.autoconfigure.exclude注意 2.x 和 3.x 配置名不同