跳到主要内容Spring Boot 自动配置:原理、条件注解与手写 Starter | 极客日志Javajava
Spring Boot 自动配置:原理、条件注解与手写 Starter
从Spring Boot自动配置的原理出发,剖析了@EnableAutoConfiguration、条件注解族和@ConfigurationProperties的协作方式。通过一个自定义Starter的实战,展示了属性绑定、Bean覆盖和注册文件的配置。还涉及调试手段和常见问题,帮助开发者从使用过渡到扩展。
自动配置是 Spring Boot 最具辨识度的特性之一。它在启动时根据 classpath 中的依赖、配置属性和已存在的 Bean,自动装配合理的默认组件,让你少写很多样板代码。但会用是一回事,能自己写一个 Starter 又是另一回事。下面我们先把自动配置的原理理顺,再动手实现一个最小的 Starter,最后谈调试和踩坑。

自动配置是怎么回事
传统的 Spring 项目需要你在 XML 或配置类中显式声明每一个 Bean。Spring Boot 的做法是:启动时扫描所有候选的自动配置类,然后根据条件注解判断哪些该生效。整个过程由 @SpringBootApplication -> @EnableAutoConfiguration 触发。
@SpringBootApplication 其实是一个组合注解,里面就包含了 @EnableAutoConfiguration。它做的事情是导入所有声明好的自动配置类。这些类本身也是 @Configuration,内部会注册符合条件的具体 Bean。
加载流程:从 run() 到 Bean 注册
@SpringBootApplication
public class DemoApp {
public static void main(String[] args) {
SpringApplication.run(DemoApp.class, args);
}
}
当 run() 执行后,Spring Boot 会读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3 新文件)或 spring.factories(Spring Boot 2.x),获得自动配置类的全类名列表。然后逐个解析,对于每个配置类,评估它上面所有的条件注解。只有全部满足,才会执行里边的 @Bean 方法。
举个例子,在 Spring Boot 3 中,你的自定义 Starter 可以这样声明:
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.starter.DemoFeatureAutoConfiguration
而在 2.x 中则需要:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.starter.DemoFeatureAutoConfiguration
框架会在合适的位置导入这些类,然后条件评估。

条件注解:何时装配的决策树
@ConditionalOnClass:类路径存在指定类时生效。适合做可选依赖的自动配置。比如有 HikariCP 时才启用 DataSource 自动配置。
@ConditionalOnMissingBean:容器中没有该类型的 Bean 时才注册。这是实现'用户覆盖默认 Bean'的关键。
@ConditionalOnProperty:根据配置属性值决定。往往用来做功能开关。
@AutoConfiguration
@ConditionalOnClass(name = "com.zaxxer.hikari.HikariDataSource")
public class DataSourceAutoConfiguration {
}
@AutoConfiguration
@EnableConfigurationProperties(DemoFeatureProperties.class)
@ConditionalOnProperty(prefix = "demo.feature", name = "enabled", matchIfMissing = true)
public class DemoFeatureAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DemoService demoService(DemoFeatureProperties props) {
return new DemoService(props.getEndpoint());
}
}
matchIfMissing = true 意味着没配置属性时默认启用,这对于开箱即用很方便。
有时候还需要控制自动配置的先后顺序。Spring Boot 提供了 @AutoConfiguration(before = ...) 和 @AutoConfiguration(after = ...):
@AutoConfiguration(before = DataSourceAutoConfiguration.class)
public class MetricsAutoConfiguration {
}
如果内置的条件注解满足不了,你还可以自己实现 Condition 接口,通过 @Conditional 引入。下面这个例子只会在 profile 为 prod 时才加载:
public class OnProdProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String[] profiles = context.getEnvironment().getActiveProfiles();
for (String p : profiles) {
if ("prod".equalsIgnoreCase(p)) return true;
}
return false;
}
}
@AutoConfiguration
@Conditional(OnProdProfileCondition.class)
public class ProdOnlyAutoConfiguration {
}
把配置属性绑起来:@ConfigurationProperties
通过 @ConfigurationProperties 可以把 YAML 或 properties 中的层级配置映射到一个类型安全的 Java Bean 上。这比到处用 @Value 清晰得多,也方便校验和文档生成。
@ConfigurationProperties(prefix = "demo.feature")
public class DemoFeatureProperties {
private boolean enabled = true;
private String endpoint = "/demo";
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
public String getEndpoint() { return endpoint; }
public void setEndpoint(String endpoint) { this.endpoint = endpoint; }
}
然后在自动配置类上通过 @EnableConfigurationProperties 激活,属性对象就可以直接注入到各个 @Bean 方法了。
demo:
feature:
enabled: true
endpoint: /api/demo
实战:手写一个 Starter
动手写一个 Starter 其实不复杂。我就以提供一个 DemoService 为例,它根据配置路径返回一个欢迎信息,并且允许应用自己覆盖默认实现。
项目结构与依赖
典型的 Starter 是一个单独的 Maven 模块,依赖了 Spring Boot Autoconfigure 和 Configuration Processor。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>3.3.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
scope=provided 是因为实际应用中会由 Spring Boot 主工程引入,Starter 不需要打包进去。
编写业务 Bean 和自动配置
public class DemoService {
private final String endpoint;
public DemoService(String endpoint) {
this.endpoint = endpoint;
}
public String handle(String name) {
return "Hello, " + name + " via " + endpoint;
}
}
然后是属性类和自动配置类的结合——前面已经给出,这里再完整贴一下:
@ConfigurationProperties(prefix = "demo.feature")
public class DemoFeatureProperties {
private boolean enabled = true;
private String endpoint = "/demo";
}
@AutoConfiguration
@EnableConfigurationProperties(DemoFeatureProperties.class)
@ConditionalOnProperty(prefix = "demo.feature", name = "enabled", matchIfMissing = true)
public class DemoFeatureAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public DemoService demoService(DemoFeatureProperties props) {
return new DemoService(props.getEndpoint());
}
}
最后别忘了在 META-INF 下创建导入文件。Spring Boot 3 用户放在 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面写:
com.example.starter.DemoFeatureAutoConfiguration
如果是 2.x,还是用 spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.starter.DemoFeatureAutoConfiguration
接入应用与覆盖
消费者引入 Starter 依赖后,主类不需要特殊处理,就可以直接注入 DemoService:
@SpringBootApplication
public class DemoConsumerApp {
public static void main(String[] args) {
SpringApplication.run(DemoConsumerApp.class, args);
}
}
@RestController
public class DemoController {
private final DemoService demoService;
public DemoController(DemoService demoService) {
this.demoService = demoService;
}
@GetMapping("/hello")
public String hello(@RequestParam String name) {
return demoService.handle(name);
}
}
如果想自己提供一个覆盖实现,只需要在任意配置类里声明一个同类型的 @Bean:
@Configuration
public class CustomConfig {
@Bean
public DemoService demoService() {
return new DemoService("/custom");
}
}
由于我们有 @ConditionalOnMissingBean,Spring Boot 会优先使用你提供的这个 Bean。
调试与观测
自动配置没生效是常见问题。最直接的手段是打开 Debug 报告:在 application.properties 里加 debug=true,启动时控制台会打印 CONDITIONS EVALUATION REPORT,告诉你哪些条件匹配了,哪些没匹配。
另一个好帮手是 Actuator。开启 env、configprops 和 conditions 端点:
management.endpoints.web.exposure.include=env,configprops,conditions
env:查看所有配置源及属性。
configprops:查看 @ConfigurationProperties 绑定结果。
conditions:汇总条件评估,类似 debug 报告,但可以随时通过 HTTP 查看。
性能方面,自动配置本身一般不会成为瓶颈,但如果配置类数量庞大,建议控制粒度,避免不必要的类加载和条件评估。像 @ConditionalOnClass 这种依赖类路径检查的就要比 @ConditionalOnProperty 耗时稍高一点点,不过实际影响通常很小。更值得关注的是不要在自动配置阶段做初始化操作,可以把 Bean 的初始化推迟到第一次调用。
避坑与建议
- Bean 重复定义:如果忘记加
@ConditionalOnMissingBean,用户自定义的同名 Bean 会冲突。发现冲突时,检查一下是否真的需要覆盖,通常推荐用 @ConditionalOnMissingBean 提供默认实现。
- 属性绑定不上:先确认前缀和层级是否与配置类匹配;其次类型要对,比如你要读取的是
enabled,它应该是 boolean 而不能是 String。可以用 configprops 端点快速验证。
- 发布 Starter 时:注意标明适配的 Boot 版本;写好属性文档;避免引入过多传递依赖;安全相关的默认值要保守。
回顾
从自动配置的整体设计,到条件注解的灵活运用,再到一个可工作的 Starter,整个过程并不复杂。深入之后会发现 Spring Boot 的一个核心哲学就是:给你省事,但不开倒车。你能覆盖、能关闭、能按需扩展。
如果你想进一步探索,有几个方向可以深挖:把多个自动配置模块组织成一个 Starter 系列,规划好它们的依赖与顺序;利用 GraalVM AOT 编译原生镜像,优化冷启动;在团队中制定 Starter 开发规范,让扩展点标准化。
一些开放问题:多模块项目如何划定自动配置的边界?高并发下哪些自动配置可以延迟初始化?这些都可以作为实际生产力提升的切入点。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online