Spring Boot 自动配置原理:从 @SpringBootApplication 到 AutoConfiguration.

举报
yd_216253085 发表于 2026/11/01 21:23:08 2026/11/01
【摘要】 拆解 @EnableAutoConfiguration 的加载链与条件装配机制。

"引入 spring-boot-starter-web 就有内嵌 Tomcat 和 DispatcherServlet",这件事看起来像魔法。拆开看,它由三层机制叠加而成:@SpringBootApplication 的复合注解、ImportSelector 的批量注册、@Conditional 的条件装配。本文沿着 Spring Boot 3.x 的源码路径,把这套机制完整还原,并给出自定义 starter 的写法。适合想理解 Boot 内部机制、或需要编写公司内部 starter 的后端开发阅读。

一、@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 = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    // ...
}

三个核心注解各司其职:

  • @SpringBootConfiguration:本质是 @Configuration,标记这是配置类。
  • @ComponentScan:扫描启动类所在包及子包。默认排除所有自动配置类(AutoConfigurationExcludeFilter),避免和 @Import 重复注册。
  • @EnableAutoConfiguration:自动配置的入口。

二、@EnableAutoConfiguration 的加载链

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

@AutoConfigurationPackage 通过 @Import(AutoConfigurationPackages.Registrar.class) 把启动类所在包注册为"自动配置包",MyBatis、JPA 的实体扫描都依赖它。

真正的加载逻辑在 AutoConfigurationImportSelector#selectImports:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    AutoConfigurationEntry autoConfigurationEntry =
            getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

getAutoConfigurationEntry 做了四件事:

  1. 从注解元数据读取 exclude、excludeName,合并到排除集合。
  2. getCandidateConfigurations 通过 ImportCandidates.load(AutoConfiguration.class, classLoader) 读取候选类名。
  3. 用 AutoConfigurationImportFilter 做初步过滤,实际是 OnClassCondition、OnBeanCondition、OnWebApplicationCondition 三个过滤器。
  4. 触发 AutoConfigurationImportEvent,并做去重。

2.1 候选类从哪里来

Spring Boot 2.7 之前读的是 META-INF/spring.factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfig.FooAutoConfiguration,\
com.example.autoconfig.BarAutoConfiguration

Spring Boot 2.7 引入新机制,3.x 完全改为 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:

com.example.autoconfig.FooAutoConfiguration
com.example.autoconfig.BarAutoConfiguration

每行一个全限定类名,不再有 key=value 结构,也不再支持 @AutoConfiguration 之外的组织方式。Spring Boot 3.x 仍会读 spring.factories 中其他 key(如 EnvironmentPostProcessor),但 EnableAutoConfiguration 这个 key 已被移除支持。

2.2 排序与去重

自动配置类之间用 @AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder 声明顺序。例如 DataSourceAutoConfiguration 必须在 MybatisAutoConfiguration 之前完成,否则 SqlSessionFactory 拿不到数据源。底层由 AutoConfigurationSorter 做拓扑排序。

去重通过 Set 和 LinkedHashSet 保证,同一个配置类被多个 starter 引入时只注册一次。

三、条件装配:@Conditional 家族

自动配置类通常长这样:

@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
          DataSourceCheckpointRestoreConfiguration.class })
public class DataSourceAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @Conditional(PooledDataSourceCondition.class)
    @ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
    @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class })
    protected static class PooledDataSourceConfiguration {
    }
}

常见条件注解:

| 注解 | 判断依据 | 典型用途 |

| --- | --- | --- |

| @ConditionalOnClass | 类路径存在指定类 | starter 检测依赖是否引入 |

| @ConditionalOnMissingBean | 容器中没有指定类型/名称的 Bean | 允许用户覆盖默认实现 |

| @ConditionalOnBean | 容器中存在指定 Bean | 依赖其他配置的产物 |

| @ConditionalOnProperty | 配置项匹配 | 功能开关 |

| @ConditionalOnWebApplication | 应用类型是 SERVLET/REACTIVE | 区分 Web 与普通应用 |

| @ConditionalOnResource | 资源文件存在 | 读取可选配置 |

| @ConditionalOnExpression | SpEL 表达式为真 | 复杂组合条件 |

@ConditionalOnClass 的实现有个细节:不能直接写 DataSource.class 引用,否则类不存在时加载注解本身就会抛 NoClassDefFoundError。Spring Boot 用 OnClassCondition 配合 ASM 读取注解元数据,避免真正加载类。

3.1 条件评估的时机

条件在 ConfigurationClassPostProcessor#processConfigBeanDefinitions 阶段评估,此时只注册了 BeanDefinition,Bean 还没实例化。因此 @ConditionalOnBean 只能看到已经注册的 Bean 定义,与声明顺序强相关。在普通 @Configuration 类里用 @ConditionalOnBean 容易因为顺序问题失效,官方建议只在自动配置类中使用。

四、属性绑定

@EnableConfigurationProperties 把 @ConfigurationProperties 类注册为 Bean,配合 @ConfigurationPropertiesScan 可批量扫描。

@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(String host, int port, String username, String password) {
}

Spring Boot 3.x 支持构造器绑定(含 record),要求类只有一个有参构造器,或用 @ConstructorBinding 显式指定(3.0 起单构造器时可省略)。松散绑定规则把 app.mail.username、app.mail.user-name、APP_MAIL_USERNAME 都映射到同一属性。

绑定失败时启动直接报错,这是 Boot 的默认行为,比运行时 NullPointerException 好排查得多。

五、自定义配置的优先级

自动配置的"退让"机制依赖一个前提:用户配置先于自动配置注册。@ConditionalOnMissingBean 只有在用户 Bean 已经注册时才会跳过默认实现。

Spring Boot 保证这一点的做法是:自动配置类通过 @Import 导入,而 @ComponentScan 扫描的用户配置类在 ConfigurationClassParser 的解析顺序中更靠前。ConfigurationClassParser#processConfigurationClass 会先处理 @ComponentScan,再处理 @Import,所以用户 Bean 定义先进入容器。

配置属性的覆盖顺序则是另一套规则,优先级从高到低:

  1. 命令行参数 --server.port=9090
  2. SPRING_APPLICATION_JSON
  3. Servlet 的 ServletConfig 与 ServletContext 参数
  4. JNDI
  5. Java 系统属性 -Dserver.port=9090
  6. 操作系统环境变量 SERVER_PORT
  7. application-{profile}.properties/yml(jar 外部)
  8. application-{profile}.properties/yml(jar 内部)
  9. application.properties/yml(jar 外部)
  10. application.properties/yml(jar 内部)
  11. @PropertySource
  12. 默认值 SpringApplication.setDefaultProperties

环境变量名到属性名的映射规则:大写、点换下划线、短横线换下划线,例如 spring.datasource.url 对应 SPRING_DATASOURCE_URL。这一条在容器化部署时特别重要,K8s 的 ConfigMap 注入通常走这条路。

六、自己写一个 starter

6.1 目录结构

mail-spring-boot-starter/
├── pom.xml
└── src/main/
    ├── java/com/example/mail/
    │   ├── MailProperties.java
    │   ├── MailClient.java
    │   └── MailAutoConfiguration.java
    └── resources/META-INF/spring/
        └── org.springframework.boot.autoconfigure.AutoConfiguration.imports

6.2 自动配置类

@AutoConfiguration
@ConditionalOnClass(MailClient.class)
@EnableConfigurationProperties(MailProperties.class)
public class MailAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public MailClient mailClient(MailProperties properties) {
        return new MailClient(properties.host(), properties.port());
    }
}

@AutoConfiguration 等价于 @Configuration(proxyBeanMethods = false) 并额外支持 after、before 属性,是 Boot 3 推荐的写法。

6.3 注册文件

src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:

com.example.mail.MailAutoConfiguration

不需要 spring.factories。如果项目同时要兼容 Boot 2.x,两个文件都放,spring.factories 中的 key 仍是 org.springframework.boot.autoconfigure.EnableAutoConfiguration。

6.4 用户覆盖

业务方想自定义 MailClient 时,只需自己声明一个同类型 Bean,@ConditionalOnMissingBean 会让默认实现退让。这就是"约定优于配置"的落点。

七、排查自动配置问题

Boot 提供了三种定位手段,按成本从低到高排列。

第一种是启动参数 debug=true。它在控制台输出 ConditionEvaluationReport,分为 Positive matches(生效)与 Negative matches(未生效)两段,每条都附带原因:

DataSourceAutoConfiguration matched:
      - @ConditionalOnClass found required classes 'javax.sql.DataSource',
        'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)

DataSourceAutoConfiguration#dataSource matched:
      - @ConditionalOnMissingBean (types: javax.sql.DataSource; SearchStrategy: all)
        did not find any beans (OnBeanCondition)

DataSourceAutoConfiguration.JdbcTemplateConfiguration#jdbcTemplate did not match:
      - @ConditionalOnSingleCandidate (types: javax.sql.DataSource; SearchStrategy: all)
        did not find a bean (OnBeanCondition)

第二种是直接注入 ConditionEvaluationReport 这个 Bean,在运行期以编程方式读取评估结果,适合暴露到健康检查端点。

第三种是 spring.autoconfigure.exclude。确认某个自动配置确实不需要时,用配置显式排除,比在启动类上写 exclude 更利于多环境差异化管理:

spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration

八、常见坑 / 最佳实践

  1. 自动配置类不要放在启动类所在包及其子包下。否则会被 @ComponentScan 重复扫描,@ConditionalOnMissingBean 的判断时机错乱。放在独立的 starter 包中,靠 AutoConfiguration.imports 加载。
  2. @ConditionalOnBean 写在普通配置类里。条件评估依赖注册顺序,普通配置类的顺序不受控,容易时灵时不灵。自动配置类有拓扑排序保障,才适合用它。
  3. 升级到 Boot 3 后 spring.factories 里的自动配置不生效。3.x 不再读取 EnableAutoConfiguration key,必须迁移到 AutoConfiguration.imports。这是升级时最容易踩的坑。
  4. 自动配置类加 @ComponentScan。会导致扫描范围失控,把使用方的 Bean 也扫进来。自动配置类只应通过 @Bean 和 @Import 注册自己需要的组件。
  5. 用 @ConditionalOnProperty 却忘了 matchIfMissing。默认值为 false,未配置时条件不成立,功能静默关闭。需要默认开启时显式写 matchIfMissing = true。
  6. 排查时打开 debug=true。application.properties 加 debug=true 会打印 ConditionEvaluationReport,逐条列出哪些自动配置命中、哪些未命中及原因,比猜快得多。

九、小结

  • @SpringBootApplication = @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan。
  • @EnableAutoConfiguration 通过 @Import(AutoConfigurationImportSelector.class) 批量导入配置类。
  • Spring Boot 3.x 从 META-INF/spring/...AutoConfiguration.imports 读取候选类,不再使用 spring.factories。
  • @ConditionalOnClass、@ConditionalOnMissingBean 等条件注解决定配置类是否生效,是"约定优于配置"的实现手段。
  • 自定义 starter 只需三步:写自动配置类、声明条件、放入 imports 文件。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。