Spring Boot 自动配置(Auto-configuration)是「约定优于配置」的落地核心:它让你加上依赖就能直接用,而无需手动声明一堆 Bean。理解自动配置原理,不仅能读懂框架源码,还能写出自己的自定义 Starter,把团队的通用能力沉淀成一行依赖。本文先拆解原理,再手把手写一个可复用的 Starter。
一、自动配置到底解决了什么问题
在没有自动配置之前,整合一个数据源、一个 Web 框架或一套安全组件,往往要写几十行 XML 或 Java Config。Spring Boot 的做法是:在 classpath 里「探测」到某类或某依赖存在时,才自动帮你把对应的 Bean 装配好。你只管写业务代码,基础设施由框架兜底。这也是为什么 Spring Boot Actuator 健康指标端点 这类能力能开箱即用的原因。
但「自动」不是「无脑」。当自动配置不符合你的需求时,你需要知道它何时生效、如何覆盖、又如何把自己写的能力也做成同样的自动体验。这正是本文的重点。
二、自动配置的核心机制:条件注解
自动配置的灵魂是 @Conditional 及其派生注解。它们像一个开关:只有当某个条件成立(类存在、Bean 缺失、属性匹配、容器里没有某类型 Bean)时,对应的配置类才会被注册。下面这张表列出了最常用的几个条件注解,也是写自定义 Starter 时最该记住的工具。
| 条件注解 | 生效条件 | 典型用途 |
|---|---|---|
| @ConditionalOnClass | classpath 中存在指定类 | 探测到某依赖才装配 |
| @ConditionalOnMissingBean | 容器中尚无该类型 Bean | 允许用户自定义覆盖 |
| @ConditionalOnProperty | 配置项匹配指定值 | 用配置开关启停功能 |
| @ConditionalOnWebApplication | 当前是 Web 应用 | 仅在 Web 环境装配 |
| @ConditionalOnBean | 容器中已存在指定 Bean | 依赖其他自动配置结果 |
2.1 spring.factories 与 AutoConfiguration.imports
老版本 Spring Boot 通过 META-INF/spring.factories 里的 EnableAutoConfiguration key 列举自动配置类。从 2.7 起官方推荐改为 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,每行一个配置类的全限定名,加载更快也更清晰。Spring Boot 3 已彻底移除对 spring.factories 中自动配置条目的支持,迁移时务必注意——这也是 Spring Boot 3 升级踩坑 里最容易被忽略的一点。
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.demo.autoconfigure.DemoAutoConfiguration
三、@EnableAutoConfiguration 的加载链路
当你在启动类上写 @SpringBootApplication 时,它组合了 @EnableAutoConfiguration。框架在启动时通过 SpringFactoriesLoader(新版本改为读取 imports 文件)把所有候选自动配置类读进来,再逐一对它们求值条件注解。满足条件的配置类才会进入容器,且所有自动配置类都排在用户自定义配置之后,所以你的 Bean 永远能覆盖自动配置。
理解这一点很关键:自动配置类之所以「可被覆盖」,正是因为 @ConditionalOnMissingBean 的存在。只要你在自己的代码里声明了同名 Bean,框架的自动版本就会自动让位。
四、控制自动配置的顺序
自动配置之间也有依赖关系。Spring Boot 提供三个注解来声明顺序:@AutoConfigureBefore、@AutoConfigureAfter 和 @AutoConfigureOrder。例如你的 Starter 依赖数据源,就应该用 @AutoConfigureAfter(DataSourceAutoConfiguration.class) 保证它在数据源就绪之后再装配,避免空指针。
五、手把手写一个自定义 Starter
假设我们要封装一个「请求签名校验」组件:只要引入依赖并配置密钥,就自动注册一个拦截器。目标是让使用方零配置即可生效。
5.1 模块拆分:autoconfigure 与 starter
推荐把自动配置逻辑放到一个 xxx-autoconfigure 模块,再用一个几乎为空的 xxx-starter 模块做依赖聚合。这样使用方引入 starter 即可,符合官方的命名约定,也方便其它项目单独引用 autoconfigure。
<!-- demo-starter 的 pom.xml:只做聚合 -->
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>demo-autoconfigure</artifactId>
<version>1.0.0</version>
</dependency>
</dependency>
5.2 定义配置属性类
用 @ConfigurationProperties 把 application.yml 里的配置安全绑定到强类型对象,并加上 JSR-303 校验,避免运行期才发现问题。
@ConfigurationProperties(prefix = "demo.sign")
@Validated
public class SignProperties {
/** 是否启用签名校验,默认开启 */
private boolean enabled = true;
/** 签名密钥 */
@NotBlank
private String secret;
/** 允许的时间偏移秒数 */
private long toleranceSeconds = 300;
// getter / setter 省略
}
5.3 编写自动配置类
核心配置类用条件注解约束生效范围:类路径存在 Web 相关类、且用户尚未自己定义时才装配。注意始终用 @ConditionalOnMissingBean 给用户留覆盖口子。
@AutoConfiguration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(HandlerInterceptor.class)
@EnableConfigurationProperties(SignProperties.class)
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
public class DemoAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "demo.sign", name = "enabled", havingValue = "true", matchIfMissing = true)
public SignInterceptor signInterceptor(SignProperties properties) {
return new SignInterceptor(properties.getSecret(), properties.getToleranceSeconds());
}
@Bean
@ConditionalOnMissingBean
public SignWebMvcConfigurer signWebMvcConfigurer(SignInterceptor interceptor) {
return new SignWebMvcConfigurer(interceptor);
}
}
5.4 注册自动配置
在 autoconfigure 模块的 resources/META-INF/spring/ 下新建 imports 文件,写入配置类全限定名即可。Spring Boot 启动时会自动扫描并加载它。
# org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.demo.autoconfigure.DemoAutoConfiguration
5.5 使用方如何引入
使用方只需引入 starter 并在配置里填密钥,拦截器便自动生效,无需任何 Java Config。这和你使用 Spring Security 的 JWT 认证 或 Spring WebFlux 响应式接口 时的「引入即用」体验完全一致。
# application.yml
demo:
sign:
enabled: true
secret: ${SIGN_SECRET}
tolerance-seconds: 300
六、让 Starter 更聪明:条件组合的实战技巧
好的 Starter 不该「强行注入」。常用技巧包括:用 @ConditionalOnProperty 做总开关(默认开启、可一键关闭);用 @ConditionalOnMissingBean 尊重用户的自定义;用 ObjectProvider 注入可选依赖,避免强耦合。当一个能力依赖另一个自动配置的结果时,用 @ConditionalOnBean 表达「它存在我才生效」。
另外,把 Bean 的创建逻辑放进 @Bean 方法而非字段初始化,保证条件注解能真正拦截。很多人踩的坑是:条件不满足时类还是被实例化,根源就是忽略了这一点。
七、测试你的 Starter
自动配置必须用 ApplicationContextRunner 做隔离测试:模拟不同 classpath 与配置,断言 Bean 是否按预期注册。把这类测试接进 GitHub Actions CI 流水线,每次提交都验证条件分支,能极大降低「别人用不了」的风险。
private final ApplicationContextRunner runner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(DemoAutoConfiguration.class));
@Test
void shouldRegisterInterceptorWhenEnabled() {
runner.withPropertyValues("demo.sign.secret=abc")
.run(context -> assertThat(context).hasBean("signInterceptor"));
}
@Test
void shouldNotRegisterWhenDisabled() {
runner.withPropertyValues("demo.sign.enabled=false", "demo.sign.secret=abc")
.run(context -> assertThat(context).doesNotHaveBean("signInterceptor"));
}
八、常见坑与排错清单
排错时最常用的两个命令是 spring-boot:run 配合 --debug 启动,框架会在控制台打印「Positive matches / Negative matches」,告诉你每个自动配置类为什么生效或没生效。下面是一份高频问题清单:
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| Starter 不生效 | imports 文件路径或类名写错 | 确认文件位于 META-INF/spring 且类名全限定正确 |
| Bean 被重复注册 | 忘记 @ConditionalOnMissingBean | 给用户自定义留覆盖口子 |
| 配置项不绑定 | 忘记 @EnableConfigurationProperties | 在配置类上显式开启属性绑定 |
| 启动报 NoClassDefFound | 依赖未用 compileOnly/optional | 把可选依赖标记为 optional 避免传递 |
如果你的 Starter 需要复杂的能力编排,也可以借鉴 Spring Modulith 模块化单体 的思路,把自动配置按领域拆成多个配置类,再用 imports 文件统一编排,保持可维护性。
九、小结
Spring Boot 自动配置原理的本质,是「条件注解 + 约定式加载 + 用户优先覆盖」。掌握它,你既能读懂框架为何如此智能,也能把团队的公共能力封装成一行依赖的自定义 Starter。记住三件事:条件注解控制开关、@ConditionalOnMissingBean 永远给用户留后路、用 ApplicationContextRunner 把行为测透。当你开始用 Starter 沉淀能力时,Spring Cloud 微服务 级别的复用也就水到渠成了。




