Spring Boot Starter 全过程原理
Starter 不是“把几个依赖放一起”这么简单。真正能在商业项目里复用的 Starter,必须同时具备依赖聚合、自动配置发现、条件装配、配置绑定、默认 Bean、用户覆盖、监控指标、健康检查、失败诊断和线上排查能力。
一句话先建立直觉:
Starter 是一个场景化接入入口;autoconfigure 提供默认装配;Spring Boot 通过自动配置清单发现配置类,再用条件注解决定是否创建 Bean。
学习目标
学完这一页,你要能说清楚:
- Starter、autoconfigure、client SDK、业务项目的边界。
- 为什么生产项目推荐拆成
xxx-starter和xxx-autoconfigure。 - Starter 引入后为什么能开箱即用。
- Boot 2 的
spring.factories和 Boot 3 的AutoConfiguration.imports怎么写。 @ConfigurationProperties、@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty各自负责什么。- 自定义 Starter 从 0 到可用需要哪些文件。
- 为什么公共 Starter 不能强行创建 Bean。
- Starter 怎么支持健康检查、指标、失败诊断。
- 自动配置没生效、配置没绑定、Bean 冲突怎么排查。
- 商业项目里哪些能力适合沉淀成 Starter。
Starter 解决什么问题
假设公司有 20 个微服务都要调用医院 HIS 接口。没有 Starter 时,每个服务都要重复做:
- 引入 HTTP 客户端依赖。
- 写
HospitalClientBean。 - 写医院地址、超时、鉴权配置。
- 写健康检查。
- 写指标。
- 写失败重试和日志。
- 写启动时配置校验。
结果很容易变成:
| 问题 | 后果 |
|---|---|
| 依赖版本不一致 | 有的服务能跑,有的服务启动失败 |
| Bean 写法不一致 | 超时、重试、鉴权行为不统一 |
| 配置项命名不一致 | 运维和接入方难理解 |
| 没有健康检查 | 依赖挂了监控不知道 |
| 没有失败诊断 | 缺配置时只抛空指针 |
Starter 的目标是把公共接入方式标准化:
flowchart TD
A["业务项目引入 hospital-spring-boot-starter"] --> B["依赖自动进入 classpath"]
B --> C["Spring Boot 发现自动配置类"]
C --> D["绑定 hospital.client 配置"]
D --> E["按条件创建 HospitalClient"]
E --> F["注册健康检查和指标"]
F --> G["业务代码直接注入使用"]模块边界
生产项目里推荐至少拆三层:
hospital-client-sdk
hospital-spring-boot-autoconfigure
hospital-spring-boot-starter| 模块 | 职责 | 是否依赖 Spring Boot |
|---|---|---|
hospital-client-sdk | 真正的客户端能力,比如 HTTP 调用、签名、重试 | 尽量不依赖 |
hospital-spring-boot-autoconfigure | 自动配置类、属性绑定、条件装配、健康检查 | 依赖 |
hospital-spring-boot-starter | 依赖聚合入口,通常几乎不写代码 | 依赖聚合 |
为什么要拆?
- SDK 可以被非 Spring Boot 项目使用。
- autoconfigure 可以单独测试自动配置逻辑。
- starter 只做入口,避免职责混乱。
- 依赖升级和版本管理更清楚。
Starter 开箱即用全过程
flowchart TD
A["业务项目添加 starter 依赖"] --> B["starter 引入 autoconfigure 和 sdk"]
B --> C["自动配置声明文件进入 classpath"]
C --> D["AutoConfigurationImportSelector 读取候选配置"]
D --> E["条件注解判断"]
E --> F{"条件匹配吗"}
F -- "否" --> G["跳过"]
F -- "是" --> H["绑定配置属性"]
H --> I["注册默认 BeanDefinition"]
I --> J["refresh 阶段创建 Bean"]
J --> K["业务 Service 注入使用"]关键点:
- starter 本身不神奇,真正装配来自 autoconfigure。
- 自动配置类必须能被 Spring Boot 发现。
- 条件注解决定是否生效。
- 最终仍然回到 Spring IOC 创建 Bean。
Boot 2 和 Boot 3 声明文件
Boot 2 写法
文件路径:
src/main/resources/META-INF/spring.factories内容:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.hospital.autoconfigure.HospitalClientAutoConfigurationBoot 3 写法
文件路径:
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports内容:
com.example.hospital.autoconfigure.HospitalClientAutoConfiguration对比:
| 对比项 | Boot 2 | Boot 3 |
|---|---|---|
| 常见 JDK | JDK 8、JDK 11 | JDK 17+ |
| 声明文件 | META-INF/spring.factories | META-INF/spring/...AutoConfiguration.imports |
| 写法 | key-value | 每行一个配置类 |
| 老项目是否常见 | 非常常见 | 新项目推荐 |
你不能只会 Boot 3。很多商业系统仍然是 JDK 8 + Spring Boot 2.x,面试和维护老项目时必须能看懂 spring.factories。
从 0 写一个医院客户端 Starter
下面用“医院接口客户端”举例,比短信 Demo 更贴近商业系统。
1. SDK 模块
package com.example.hospital.client;
public class HospitalClient {
private final String baseUrl;
private final int connectTimeoutMillis;
private final String accessKey;
public HospitalClient(String baseUrl, int connectTimeoutMillis, String accessKey) {
this.baseUrl = baseUrl;
this.connectTimeoutMillis = connectTimeoutMillis;
this.accessKey = accessKey;
}
public String queryPatient(String patientId) {
return "query " + patientId + " from " + baseUrl;
}
public boolean ping() {
return baseUrl != null && baseUrl.startsWith("http");
}
}SDK 只表达客户端能力,不关心 Spring Boot 怎么装配。
2. 配置属性类
package com.example.hospital.autoconfigure;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hospital.client")
public class HospitalClientProperties {
private boolean enabled = true;
private String baseUrl;
private int connectTimeoutMillis = 3000;
private String accessKey;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getBaseUrl() {
return baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public int getConnectTimeoutMillis() {
return connectTimeoutMillis;
}
public void setConnectTimeoutMillis(int connectTimeoutMillis) {
this.connectTimeoutMillis = connectTimeoutMillis;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
}配置项示例:
hospital:
client:
enabled: true
base-url: https://his.example.com
connect-timeout-millis: 3000
access-key: ${HOSPITAL_ACCESS_KEY}为什么用 @ConfigurationProperties,而不是一堆 @Value?
- 一组配置有统一前缀。
- 支持嵌套对象。
- 支持类型转换。
- 可以加校验。
- IDE 可以生成配置提示。
3. 自动配置类
package com.example.hospital.autoconfigure;
import com.example.hospital.client.HospitalClient;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass(HospitalClient.class)
@EnableConfigurationProperties(HospitalClientProperties.class)
@ConditionalOnProperty(
prefix = "hospital.client",
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public class HospitalClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public HospitalClient hospitalClient(HospitalClientProperties properties) {
if (properties.getBaseUrl() == null || properties.getBaseUrl().trim().isEmpty()) {
throw new HospitalClientConfigException("hospital.client.base-url 不能为空");
}
return new HospitalClient(
properties.getBaseUrl(),
properties.getConnectTimeoutMillis(),
properties.getAccessKey()
);
}
}每个注解的意义:
| 注解 | 作用 |
|---|---|
@AutoConfiguration | 表示这是 Boot 自动配置类 |
@ConditionalOnClass | SDK 存在时才装配 |
@EnableConfigurationProperties | 开启配置属性绑定 |
@ConditionalOnProperty | 支持配置开关 |
@ConditionalOnMissingBean | 用户自定义 Bean 优先,默认 Bean 让位 |
4. Boot 3 自动配置声明
文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports内容:
com.example.hospital.autoconfigure.HospitalClientAutoConfiguration如果这个文件漏了,业务项目引入 starter 后,自动配置类根本不会进入候选列表。
5. starter 模块 pom
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>hospital-spring-boot-autoconfigure</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>hospital-client-sdk</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>starter 模块一般不写复杂代码,只负责让业务项目少引依赖。
6. 业务项目使用
import com.example.hospital.client.HospitalClient;
import org.springframework.stereotype.Service;
@Service
public class PatientService {
private final HospitalClient hospitalClient;
public PatientService(HospitalClient hospitalClient) {
this.hospitalClient = hospitalClient;
}
public String getPatient(String patientId) {
return hospitalClient.queryPatient(patientId);
}
}业务项目不需要写 HospitalClient 的 @Bean。如果需要接管,也可以自己定义:
import com.example.hospital.client.HospitalClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CustomHospitalClientConfig {
@Bean
public HospitalClient hospitalClient() {
return new HospitalClient("https://custom.example.com", 1000, "custom-key");
}
}因为自动配置用了 @ConditionalOnMissingBean,默认 Bean 会让位。
加上健康检查
商业 Starter 不能只创建客户端,还要能被监控系统理解。
package com.example.hospital.autoconfigure;
import com.example.hospital.client.HospitalClient;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
public class HospitalClientHealthIndicator implements HealthIndicator {
private final HospitalClient hospitalClient;
public HospitalClientHealthIndicator(HospitalClient hospitalClient) {
this.hospitalClient = hospitalClient;
}
@Override
public Health health() {
try {
if (hospitalClient.ping()) {
return Health.up().withDetail("hospitalClient", "available").build();
}
return Health.down().withDetail("hospitalClient", "unavailable").build();
} catch (Exception ex) {
return Health.down(ex).build();
}
}
}自动配置中注册:
@Bean
@ConditionalOnClass(HealthIndicator.class)
@ConditionalOnMissingBean(name = "hospitalClientHealthIndicator")
public HealthIndicator hospitalClientHealthIndicator(HospitalClient hospitalClient) {
return new HospitalClientHealthIndicator(hospitalClient);
}注意:健康检查必须轻量、带超时,不能做全量业务查询。
加上指标
package com.example.hospital.autoconfigure;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
public class HospitalClientMetrics {
private final Counter success;
private final Counter failed;
public HospitalClientMetrics(MeterRegistry registry) {
this.success = Counter.builder("hospital_client_success_total")
.description("医院接口调用成功次数")
.register(registry);
this.failed = Counter.builder("hospital_client_failed_total")
.description("医院接口调用失败次数")
.register(registry);
}
public void success() {
success.increment();
}
public void failed() {
failed.increment();
}
}指标适合看整体趋势,日志适合查单个请求。公共 Starter 最好提供核心指标,避免每个业务项目各写各的。
加上失败诊断
缺少 base-url 时,如果只抛 NullPointerException,接入方体验很差。可以定义异常:
public class HospitalClientConfigException extends RuntimeException {
public HospitalClientConfigException(String message) {
super(message);
}
}失败分析器:
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
public class HospitalClientFailureAnalyzer
extends AbstractFailureAnalyzer<HospitalClientConfigException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure,
HospitalClientConfigException cause) {
return new FailureAnalysis(
"医院客户端配置不完整:" + cause.getMessage(),
"请在 application.yml 中配置 hospital.client.base-url",
cause
);
}
}Boot 2 常见声明:
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.example.hospital.autoconfigure.HospitalClientFailureAnalyzerBoot 3 也可以通过对应的工厂加载机制注册。核心思想是:把启动失败原因翻译成人能看懂的提示。
条件注解组合策略
成熟 Starter 不会只写一个条件注解。
flowchart TD
A["自动配置类"] --> B{"SDK类存在吗"}
B -- "否" --> X["跳过"]
B -- "是" --> C{"enabled=true吗"}
C -- "否" --> X
C -- "是" --> D{"用户已有Bean吗"}
D -- "有" --> E["默认Bean让位"]
D -- "没有" --> F["创建默认Bean"]推荐组合:
| 目标 | 推荐注解 |
|---|---|
| 依赖存在才启用 | @ConditionalOnClass |
| 配置开关控制 | @ConditionalOnProperty |
| 用户可覆盖 | @ConditionalOnMissingBean |
| Web 场景才启用 | @ConditionalOnWebApplication |
| 有某个基础 Bean 再启用 | @ConditionalOnBean |
不这样会怎样
| 错误做法 | 后果 |
|---|---|
| starter 里写业务规则 | 公共组件和业务强耦合,其他项目无法复用 |
不加 @ConditionalOnMissingBean | 用户无法替换默认实现,或者 Bean 冲突 |
| 默认线程池无界 | 高并发下 OOM 或线程数暴涨 |
| 自动配置类做网络连接 | 启动慢,甚至启动失败 |
| 配置没有开关 | 出问题时无法临时关闭能力 |
| 不提供健康检查 | 依赖故障无法被监控发现 |
| 不提供失败诊断 | 接入方只能看一堆堆栈 |
自动配置没生效怎么排查
flowchart TD
A["Starter 引入后 Bean 没有出现"] --> B["确认依赖是否真的引入"]
B --> C["查看 jar 中是否有自动配置声明"]
C --> D["打开 debug=true 或 actuator conditions"]
D --> E{"自动配置类在候选列表吗"}
E -- "否" --> F["检查 spring.factories 或 imports 路径"]
E -- "是" --> G{"条件匹配吗"}
G -- "否" --> H["检查 classpath、enabled、Web环境"]
G -- "是" --> I{"是否已有用户Bean"}
I -- "是" --> J["默认Bean让位"]
I -- "否" --> K["检查配置绑定和Bean创建异常"]排查清单:
mvn dependency:tree看 starter 是否真的引入。- 打开 jar,确认
META-INF/spring.factories或AutoConfiguration.imports存在。 - 类名是否写错,包名是否改过。
debug=true看条件报告。/actuator/conditions看 Positive matches 和 Negative matches。- 是否配置了
enabled=false。 - 是否缺少 SDK 依赖导致
@ConditionalOnClass不匹配。 - 是否已经有用户自定义 Bean,导致默认 Bean 让位。
- 配置绑定是否失败,例如字符串绑定 int。
- Bean 创建时是否访问外部服务导致启动失败。
商业项目适合沉淀成 Starter 的能力
| 能力 | 是否适合 | 原因 |
|---|---|---|
| 统一鉴权客户端 | 适合 | 多服务共用,配置和指标统一 |
| 医院接口客户端 | 适合 | 多服务复用,接入和监控标准化 |
| Redis Key 规范工具 | 适合 | 统一前缀、序列化、过期策略 |
| MQ 生产者封装 | 适合 | 统一重试、traceId、消息头 |
| 业务订单规则 | 不适合 | 和具体业务强绑定 |
| 某个 Controller | 不适合 | 容易污染业务路由 |
| 数据库表结构强依赖逻辑 | 谨慎 | 多项目表结构可能不一致 |
判断标准:
- 是否多个项目都会用。
- 是否属于基础设施而不是业务规则。
- 是否可以通过配置差异化。
- 是否能提供默认值并允许覆盖。
- 是否需要统一监控和排查。
面试标准回答
Starter 是什么
Starter 是 Spring Boot 的场景化依赖入口,通常负责聚合依赖,并引入 autoconfigure 模块。autoconfigure 模块提供自动配置类、配置属性类、条件注解和默认 Bean。业务项目引入 Starter 后,Spring Boot 读取自动配置声明文件,条件满足就创建默认 Bean,所以能开箱即用。
Starter 和自动配置什么关系
Starter 主要负责依赖入口,自动配置负责真正装配 Bean。starter 把 SDK 和 autoconfigure 带进 classpath,Spring Boot 再通过 spring.factories 或 AutoConfiguration.imports 发现自动配置类,经过条件注解判断后把默认 Bean 注册到容器。
为什么要拆 starter 和 autoconfigure
starter 最好只做依赖聚合,autoconfigure 放自动配置类、属性类、条件类和默认 Bean。这样职责清楚,自动配置逻辑可以单独测试,SDK 也可以独立复用,避免 starter 模块变成大杂烩。
自定义 Starter 关键点
关键点包括:定义清晰的配置前缀,使用 @ConfigurationProperties 承载配置;用 @ConditionalOnClass 判断依赖是否存在;用 @ConditionalOnProperty 提供开关;用 @ConditionalOnMissingBean 尊重用户自定义;按 Boot 2/3 版本写正确的自动配置声明文件;提供健康检查、指标和失败诊断。
Starter 没生效怎么排查
先确认依赖是否引入,再看 jar 里是否有 spring.factories 或 AutoConfiguration.imports,然后打开 debug=true 或 /actuator/conditions 看自动配置类是否在候选列表、是否被 exclude、条件是否匹配、是否已有用户 Bean、配置绑定或 Bean 创建是否异常。
