SpringNotes

第 09 章:Starter 设计

zjc 于 2026-01-09 发布

这是《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 设计原则

  1. 只自动配置自己负责的能力;
  2. 不扫描用户业务包;
  3. 默认值安全保守;
  4. 允许用户覆盖 Bean;
  5. 属性集中且有校验;
  6. 日志有开关;
  7. 外部调用有超时;
  8. 不强制绑定特定日志实现;
  9. 提供 IDE 提示;
  10. 版本矩阵清晰。

尤其不要在 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 表示默认开启。选择默认值时要考虑:

  1. 未配置时是否应该自动创建连接;
  2. 缺少 endpoint 时是否应该失败;
  3. 测试环境是否需要禁用;
  4. 是否会让使用方启动变慢。

推荐:

必需 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

日志规范:

  1. 不打印手机号全文;
  2. 不打印验证码;
  3. 记录 requestId;
  4. 失败记录原因;
  5. debug 日志可关闭。

9.9 文档与提示

必须提供:

  1. 引入坐标;
  2. 支持版本矩阵;
  3. 配置表;
  4. 默认值;
  5. 覆盖 Bean 的方式;
  6. 行为开关;
  7. 指标;
  8. 常见问题;
  9. 安全注意事项;
  10. 兼容性变更。

生成配置提示:

<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,并通过完整测试矩阵保证版本兼容。

思考题

  1. 为什么 Starter 不应该包含宽泛的 @ComponentScan
  2. 官方和第三方 Starter 命名有什么区别?
  3. 如何设计必需配置缺失时的行为?
  4. Starter 应该暴露哪些指标?
  5. 用户覆盖 Bean 的能力为什么重要?