这是《Spring Boot 与 Spring Cloud 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Starter 是依赖聚合和自动装配的组合包,目标是让使用方“引入依赖、写少量配置、即可使用能力”。设计良好的 Starter 应该有清晰边界、可控默认值、完善提示和测试。
9.1 Starter 的组成
demo-sms-spring-boot-starter
|-- pom.xml
|-- autoconfigure module
| |-- SmsAutoConfiguration
| |-- SmsProperties
| +-- META-INF/spring/*.imports
|-- starter module
| +-- 依赖聚合
+-- tests
简单项目可以合并为一个模块;多模块便于把 API、实现和自动配置分离。
职责:
| 模块 | 职责 |
|---|---|
| api | 对外接口与 DTO |
| core | 核心实现 |
| autoconfigure | Spring 条件装配 |
| starter | 只做依赖聚合 |
9.2 命名规范
官方 Starter:
spring-boot-starter-web
spring-boot-starter-data-jpa
第三方 Starter:
xxx-spring-boot-starter
不要使用 spring-boot-starter-xxx 冒充官方包。命名清晰有助于排查依赖来源和版本冲突。
9.3 设计原则
- 只自动配置自己负责的能力;
- 不扫描用户业务包;
- 默认值安全保守;
- 允许用户覆盖 Bean;
- 属性集中且有校验;
- 日志有开关;
- 外部调用有超时;
- 不强制绑定特定日志实现;
- 提供 IDE 提示;
- 版本矩阵清晰。
尤其不要在 Starter 中使用:
@ComponentScan("com.example")
这会意外扫描使用方代码,造成不可控 Bean 注册。
9.4 完整示例
属性:
@ConfigurationProperties(prefix = "demo.sms")
@Validated
public record SmsProperties(
@NotBlank String endpoint,
@NotNull Duration connectTimeout,
@NotNull Duration readTimeout,
@Valid Client client) {
public record Client(
@Min(1) @Max(200) int poolSize,
boolean compression) {
}
}
自动配置:
@AutoConfiguration
@EnableConfigurationProperties(SmsProperties.class)
@ConditionalOnClass(SmsClient.class)
public class SmsAutoConfiguration {
@Bean
@ConditionalOnMissingBean(SmsClient.class)
public SmsClient smsClient(SmsProperties properties) {
return SmsClient.builder()
.endpoint(properties.endpoint())
.connectTimeout(properties.connectTimeout())
.readTimeout(properties.readTimeout())
.build();
}
}
使用方配置:
demo:
sms:
endpoint: https://sms.example.com
connect-timeout: 1s
read-timeout: 3s
client:
pool-size: 20
compression: true
9.5 依赖管理
Starter POM 示例:
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>demo-sms-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>demo-sms-core</artifactId>
</dependency>
</dependencies>
版本策略:
| 依赖 | 建议 |
|---|---|
| Spring Boot | provided 或由父工程管理 |
| Jackson | 尽量交给 Boot 管理 |
| 日志 API | 使用 SLF4J |
| HTTP 客户端 | 声明明确版本 |
| 工具库 | 避免传递大量无关依赖 |
不要把内部实现依赖全部传递给使用方。
9.6 条件设计
常见开关:
@ConditionalOnProperty(prefix = "demo.sms", name = "enabled", havingValue = "true", matchIfMissing = true)
matchIfMissing = true 表示默认开启。选择默认值时要考虑:
- 未配置时是否应该自动创建连接;
- 缺少 endpoint 时是否应该失败;
- 测试环境是否需要禁用;
- 是否会让使用方启动变慢。
推荐:
必需 endpoint 缺失 -> 启动失败
enabled=false -> 不创建
可选行为 -> 默认关闭
外部连接 -> 默认懒创建
9.7 生命周期与资源释放
客户端持有连接池或调度器时应支持关闭:
@Bean(destroyMethod = "close")
public SmsClient smsClient(SmsProperties properties) {
return SmsClient.create(properties);
}
如果对象没有公开关闭方法,可以:
@Bean(destroyMethod = "")
public SmsClient smsClient() {
}
然后通过生命周期 Bean 管理资源。资源关闭日志要有实例标识,便于定位泄漏。
9.8 可观测性
暴露指标:
@Bean
public SmsMetrics smsMetrics(MeterRegistry registry) {
return new SmsMetrics(registry);
}
建议指标:
sms_client_request_seconds
sms_client_request_total
sms_client_failure_total
sms_client_pool_active
日志规范:
- 不打印手机号全文;
- 不打印验证码;
- 记录 requestId;
- 失败记录原因;
- debug 日志可关闭。
9.9 文档与提示
必须提供:
- 引入坐标;
- 支持版本矩阵;
- 配置表;
- 默认值;
- 覆盖 Bean 的方式;
- 行为开关;
- 指标;
- 常见问题;
- 安全注意事项;
- 兼容性变更。
生成配置提示:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
构建后生成:
META-INF/spring-configuration-metadata.json
9.10 测试矩阵
至少覆盖:
| 场景 | 断言 |
|---|---|
| 默认条件 | Bean 创建 |
| disabled | Bean 不创建 |
| 用户覆盖 | 用户 Bean 生效 |
| 缺少 endpoint | 启动失败 |
| 超时非法 | 校验失败 |
| 关闭上下文 | 资源释放 |
| 多上下文 | 无静态污染 |
| Web/非 Web | 行为一致 |
| 不同 Boot 版本 | 兼容矩阵 |
测试用户覆盖:
@SpringBootTest
@Import(CustomSmsConfiguration.class)
class OverrideTests {
@Autowired
private SmsClient smsClient;
@Test
void shouldUseCustomClient() {
assertThat(smsClient).isInstanceOf(CustomSmsClient.class);
}
}
本章小结
Starter 的价值是把复杂集成收敛为依赖、配置和默认行为。设计时要坚持职责清晰、不扫描用户包、默认值保守、属性可校验、资源可释放、指标可观测。Spring Boot 3 的自动装配注册使用 AutoConfiguration.imports,并通过完整测试矩阵保证版本兼容。
思考题
- 为什么 Starter 不应该包含宽泛的
@ComponentScan? - 官方和第三方 Starter 命名有什么区别?
- 如何设计必需配置缺失时的行为?
- Starter 应该暴露哪些指标?
- 用户覆盖 Bean 的能力为什么重要?