ElIauk

Spring Boot 自动配置原理与排查

为什么加了 starter 就能用 很多人第一次用 Spring Boot 时会有两个疑问:项目里只加了 spring-boot-starter-web 一个依赖,为什么就能直接注入 ObjectMapper、DataSource,甚至 ServletWebServerFactory?自己写一个同类

评论
Spring Boot 自动配置原理与排查

为什么加了 starter 就能用

很多人第一次用 Spring Boot 时会有两个疑问:项目里只加了 spring-boot-starter-web 一个依赖,为什么就能直接注入 ObjectMapper、DataSource,甚至 ServletWebServerFactory?自己写一个同类型的 @Bean 之后,原来那个 Bean 为什么就"让位"了?反过来,把某个 starter 排除掉,启动又立刻报 NoSuchBeanDefinitionException。

这几个现象背后都是同一套机制:自动配置。它并不神秘,本质是"提前写好一批配置类,启动时按条件筛选,满足条件才注册 Bean"。把这套流程弄清楚,"这个 Bean 从哪来""我的配置为什么不生效"这类问题就不用再靠猜。

自动配置从哪里开始

@SpringBootApplication 是一个组合注解,拆开看主要是三个:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {
    // 省略 excludeFilters 等属性
}

真正负责自动配置的是 @EnableAutoConfiguration,它通过 @Import(AutoConfigurationImportSelector.class) 把候选配置类收集进来。收集的来源随版本变过:

Spring Boot 版本 候选配置来源
2.6 及以前 各 jar 的 META-INF/spring.factories 中 EnableAutoConfiguration 键
2.7 新增 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,两种都认
3.0 及以后 只认新的 .imports 文件,spring.factories 不再用于自动配置

所以从 2.7 升级到 3.0 时,如果公司内部 starter 还在用 spring.factories 注册自动配置,升级后会出现"依赖在、Bean 没了"的情况,这是很典型的一次断点。

收集到的候选类动辄上百个,但它们不会全部生效。每个自动配置类头上都挂着一串条件注解,启动时逐个判断,只有条件成立才注册里面的 @Bean。

条件注解决定装不装

常见条件注解可以按用途记:

注解 作用 典型场景
@ConditionalOnClass 类路径存在指定类 有 DataSource 才装配数据源
@ConditionalOnMissingBean 容器里没有同类型 Bean 才生效 给用户留出覆盖入口
@ConditionalOnProperty 配置项为指定值才生效 xxx.enabled=true
@ConditionalOnWebApplication 当前是 Web 应用 只在 Servlet 环境装配
@ConditionalOnBean 容器里已有某 Bean 依赖其他自动配置的产物

这也是"自定义 Bean 优先"的原因:自动配置类里大量使用 @ConditionalOnMissingBean,你只要在能被扫描到的配置类里声明同类型 Bean,自动配置那一段就会自动跳过。

除此之外,自动配置类之间还有先后关系,常用 @AutoConfigureBefore、@AutoConfigureAfter 标注。比如 Web 相关配置要等 Servlet 容器就绪,数据源相关配置要排在前面的基础设施之后。顺序错了,条件判断时"依赖的 Bean 还没注册",配置就悄悄不生效。

排查:Bean 到底从哪来

出问题时不要先改代码,先让 Spring 把判断结果打出来。在 application.yml 里加一行:

debug: true

或者在启动命令上加 --debug,控制台会输出 Condition Evaluation Report,里面分成 Positive matches(生效的自动配置)和 Negative matches(没生效的以及原因)。如果引了 actuator,还可以直接访问:

curl http://localhost:8080/actuator/conditions

想确认某个自动配置是否被排除,看 Negative matches 里的 Exclusions 部分即可。定位到具体类以后,再回头看它的条件注解,一般就能解释"为什么没装"。

常见坑

1. 自定义 Bean 没生效

先确认这个 @Configuration 类是否在启动类所在包或子包下。@ComponentScan 默认只扫启动类所在包,多模块项目里把配置类放到 com.example.common、启动类却在 com.example.app,就会出现"代码写了但没注册"。

2. 排除自动配置后忘了补 Bean

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)

排除只是"不装配",不是"帮你配好"。排除数据源自动配置后,如果代码里还有 JdbcTemplate 依赖,启动照样失败,需要自己提供 DataSource。

3. 升级 Boot 3 后内部 starter 失效

前面说过,3.0 只认 AutoConfiguration.imports。内部 starter 要么补文件,要么升级依赖版本,不能只改 spring-boot.version 就上线。

4. 多个 BOM 版本打架

父 POM 引了 Spring Boot 2.7 的 BOM,子模块又单独指定了 3.0 的 starter 版本,编译能过,启动时报 NoSuchMethodError 或条件判断异常。统一由父 POM 管理版本,子模块不要写版本号。

5. 条件"看起来对但没生效"

@ConditionalOnProperty 的 havingValue 区分大小写,matchIfMissing 默认是 false。配置写成 enabled: TRUE、代码里写 havingValue = "true",条件就不成立。这种问题只能靠 --debug 的报告发现。

小结

自动配置可以概括成三步:启动时收集候选配置类,按条件注解过滤,把通过的配置类里的 Bean 注册进容器。排查时按"候选有没有被收集、条件是否满足、用户 Bean 是否覆盖"的顺序看,基本不会跑偏。

参考

评论

推荐阅读

订阅邮件

订阅更新,及时收到最新文章。

作者