Skip to content

自动装配原理:@EnableAutoConfiguration 是怎么工作的

本文是 Spring 系统学习系列的 L2 核心篇。前置:容器启动与 BeanDefinitionBean 生命周期扩展点。 学完可以配合面试题食用:08. Spring Boot 自动装配与 spring.factories

引入依赖就能用,是谁在背后布置 Bean

写 Spring Boot 应用时,pom.xml 里加了 spring-boot-starter-data-jpa,没写一行 XML 也没贴一个 @BeanDataSourceJdbcTemplate、事务管理器就全都有了。这个现象背后是一条确定的执行链:@EnableAutoConfiguration 注解生效时,AutoConfigurationImportSelector 会去读固定路径的配置文件,把里面列出的上百个配置类逐个做条件评估,通过的才注册成 BeanDefinition。上一篇讲过容器启动的通用流程,这一篇把"配置类从哪来、谁放行、谁拦截"这一段拆开看。

加载链:从注解到 .imports 文件

@SpringBootApplication 是个组合注解,其中 @EnableAutoConfiguration 通过 @Import(AutoConfigurationImportSelector.class) 引入了一个 ImportSelector。容器刷新阶段处理配置类的 import 时,会调用它的 selectImports(),链路如下:

mermaid
sequenceDiagram
    participant C as 配置类解析器
    participant S as AutoConfigurationImportSelector
    participant L as AutoConfigurationMetadata
    participant AC as 条件评估

    C->>S: selectImports()
    S->>S: getCandidateConfigurations()
    Note over S: SpringFactoriesLoader.load()<br/>读 META-INF/spring/<br/>org.springframework.boot.autoconfigure.AutoConfiguration.imports
    S->>L: 去重 + 排除 exclude 属性指定的类
    S->>AC: AutoConfigurationSorter 排序后逐个评估 @Conditional
    AC-->>S: 不满足的整组丢弃
    S-->>C: 返回通过的配置类全限定名

要点有三个。第一,候选列表来自 SpringFactoriesLoader.load(),它扫描 classpath 上所有 jar 的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,每行一个配置类全限定名,Boot 官方 starter 和第三方 starter 都走这条路。第二,@EnableAutoConfigurationexclude 属性在这里生效,直接从候选集里剔除。第三,排序不只是为了可读性:AutoConfigurationSorter 按字母序、@AutoConfigureOrder@AutoConfigureBefore@AutoConfigureAfter 确定评估顺序,因为一个配置类里 @ConditionalOnBean(DataSource.class) 是否成立,取决于 DataSource 的配置类有没有先被处理。

spring.factories 到 .imports 的演进

Boot 2.7 之前,自动配置类注册在 META-INF/spring.factories 里,key 是 org.springframework.boot.autoconfigure.EnableAutoConfiguration。2.7 开始迁到独立的 .imports 文件,spring.factories 中的相关条目在 Boot 3.0 彻底删除。为什么要拆?因为 spring.factories 是个大杂烩文件,内部监听器、环境后处理器、自动配置类混在一起,框架每次启动都要全量加载解析,而其中自动配置类占了绝大多数行数,既拖慢启动又没法单独裁剪。拆成纯文本的 .imports(一行一个类名)后,格式足够简单,给后续优化留了空间,加载语义也清晰了。写第三方 starter 时注意:给 Boot 3.x 用就必须用新路径,spring.factories 里的 EnableAutoConfiguration 条目不会再被读取。

条件注解族:评估时机与短路顺序

配置类进了候选集不代表会被注册,每个类上的 @Conditional 派生注解决定去留。评估发生在配置类解析阶段,也就是 BeanDefinition 注册之前,所以条件注解操纵的是"这个 Bean 定义进不进容器",而不是"这个 Bean 实例化不实例化"。常见的三员:

  • @ConditionalOnClass:classpath 上存在某个类才生效。判断的是字节码是否存在,不需要真的加载初始化它。典型用法:DataSourceAutoConfiguration 上有 @ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class }),没引 JDBC 相关依赖时整个类直接出局,连"找不到驱动类"的报错都不会有。
  • @ConditionalOnMissingBean:容器里没有指定类型的 Bean 才注册自己这个。这是"用户配置优先"的实现机制, starter 定义的只是兜底默认值。
  • @ConditionalOnProperty:Environment 里存在某属性且值匹配才生效,matchIfMissing = true 表示属性没配也当作匹配。各种 xxx.auto-enable 开关就是它。

注意短路顺序:类级别的 @ConditionalOnClass 不满足时,类体内所有 @Bean 方法和注解属性都不再处理,方法级别的条件注解也不会评估。另外 @ConditionalOnMissingBean 的判断结果依赖 Bean 的注册顺序,只对自动配置类之间的顺序有保障(靠上面说的 Sorter),用户自己 @Configuration 里定义的 Bean 一定先于自动配置被处理,所以用户的 Bean 总能赢。

走一遍 DataSourceAutoConfiguration

以"引了 JDBC starter、配了数据源、又自己定义了一个 DataSource"为例,把整条逻辑串起来:

  1. classpath 有 DataSource.class(jdbc starter 传递引入),@ConditionalOnClass 通过,配置类进入解析。
  2. 类上还有 @ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")@AutoConfigureBefore(SqlInitializationAutoConfiguration.class) 等约束,逐一评估。
  3. 内部嵌套了 EmbeddedDatabaseConditionPooledDataSourceCondition,根据是否配置了连接池相关属性,决定走内嵌库还是池化数据源。
  4. 关键在 @ConditionalOnMissingBean(DataSource.class):用户自己声明了一个 DataSource @Bean,条件不成立,Boot 的默认数据源整组跳过。日志里那句 DataSourceAutoConfiguration.DataSourceBeanMetadata... 相关条目会出现在 Negative matches 里。

"有配置类则生效,用户 Bean 优先"就是这套条件组合的结果,不是什么额外魔法。

动手实操:--debug 报告

条件评估的结果不靠猜,Boot 提供了现成的报告。写一个最简应用:

java
// src/main/java/demo/Application.java
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
xml
<!-- pom.xml,只引 web,故意不引 jdbc -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.0</version>
</dependency>

启动时加 --debug 参数:

bash
mvn spring-boot:run -Dspring-boot.run.arguments=--debug

启动日志会打印 CONDITIONS EVALUATION REPORT,分四段。挑三行看:

text
Positive matches:
-----------------
   DispatcherServletAutoConfiguration matched:
      @ConditionalOnClass found required class 'org.springframework.web.servlet.DispatcherServlet'; @ConditionalOnMissingBean (types: org.springframework.web.servlet.DispatcherServlet; SearchStrategy: all) did not find any beans

Negative matches:
-----------------
   DataSourceAutoConfiguration:
      Did not match:
         @ConditionalOnClass did not find required class 'javax.sql.DataSource'

第一段是放行的配置类及命中的条件;第二段是被拦下的及具体原因(DataSourceAutoConfiguration@ConditionalOnClass 未通过,报告展示的是 classpath 上缺失的特定类名)。报告能回答大部分"为什么我的 Bean 没生效"类问题:先看目标配置类在不在 Negative matches,再看挂在它下面的不匹配原因,基本能定位到缺依赖、缺属性或被用户 Bean 顶掉这三类。

常见误区与小结

  • 把自动装配理解成"classpath 扫描",实际是读固定路径文件 + 条件评估,不会加载和扫描任意类。类路径上多一个 jar 不会带来额外开销,除非它注册了自动配置类且条件全部通过。
  • 以为 @ConditionalOnMissingBean 在任何场景都保用户 Bean 优先。它依赖处理顺序,用户 @Configuration 先于自动配置处理没问题,但两个自动配置类之间得靠 @AutoConfigureBefore/After 显式声明。
  • 在普通 @Configuration(非自动配置类)上用 @ConditionalOnClass 期望它正常工作。条件注解的顺序保障只存在于自动配置体系内,普通配置类的解析顺序不受 Sorter 管,可能因编译顺序不同产生不一致行为,starter 的条件逻辑应放在自动配置类里。
  • 排查 Bean 没生效时先翻代码猜条件。先跑 --debug 看 Negative matches,比读源码快得多,原因就写在报告里。
  • Boot 3 项目还在往 spring.factories 里注册自动配置类。那条通道已经关闭,必须用 META-INF/spring/...AutoConfiguration.imports

小结:自动装配 = ImportSelector 加载 .imports 文件 + 条件注解逐个把关 + Sorter 保证评估顺序。它建立在容器启动和 BeanDefinition 注册这套底层机制之上,也是理解 starter 设计的前提。下一篇讲生产环境排障,会用到这里说的 --debug 报告。

参考

  • Spring Boot 官方文档 "Auto-configuration" 章节(4.1.1 Conditional Annotations、4.1.2 Test Auto-configurations)
  • org.springframework.boot.autoconfigure.AutoConfigurationImportSelector 源码(spring-boot-autoconfigure 模块)
  • Spring Boot 2.7 Release Notes 中 "Changes to Spring Autoconfiguration" 一节

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。
粤ICP备2026104257号-1