自定义 Starter

Starter 是 Spring Boot 的即插即用扩展包:引入依赖后,功能自动生效,无需手动配置 Bean。自定义 Starter 是将公司内部通用能力(统一日志、限流客户端、认证 SDK 等)标准化分发的最佳方式。


Starter 的组成

一个完整的 Starter 通常由两个 Maven 模块组成:

my-spring-boot-starter/               ← 父项目
├── my-autoconfigure/                 ← 核心:自动配置逻辑
│   ├── pom.xml
│   └── src/main/
│       ├── java/com/example/
│       │   ├── MyAutoConfiguration.java
│       │   ├── MyProperties.java
│       │   └── MyService.java
│       └── resources/META-INF/spring/
│           └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── my-starter/                       ← 门面:只声明依赖,无代码
    └── pom.xml

小型团队可以合并为一个模块(autoconfigure 兼做 starter),但官方推荐分离以保持职责清晰。


命名规范

类型命名格式示例
官方 Starterspring-boot-starter-{name}spring-boot-starter-web
第三方 / 自定义{name}-spring-boot-startermybatis-spring-boot-starter
autoconfigure 模块{name}-spring-boot-autoconfiguremybatis-spring-boot-autoconfigure

步骤一:定义配置属性类

@ConfigurationProperties(prefix = "my.client")
@Data
public class MyClientProperties {
 
    /** 服务端地址 */
    private String serverUrl = "http://localhost:8088";
 
    /** 连接超时(毫秒) */
    private int connectTimeout = 3000;
 
    /** 读取超时(毫秒) */
    private int readTimeout = 5000;
 
    /** 最大重试次数 */
    private int maxRetries = 3;
 
    /** 功能开关 */
    private boolean enabled = true;
}

步骤二:编写核心服务类

// 对外暴露的核心能力
public class MyClient {
 
    private final MyClientProperties properties;
 
    public MyClient(MyClientProperties properties) {
        this.properties = properties;
    }
 
    public String call(String endpoint) {
        // 基于 properties 发起 HTTP 调用...
        return "response from " + properties.getServerUrl() + endpoint;
    }
}

步骤三:编写自动配置类

@AutoConfiguration                                         // Spring Boot 3.x
@ConditionalOnClass(MyClient.class)                        // 类路径有该类才生效
@ConditionalOnProperty(
    prefix = "my.client",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true                                  // 未配置时默认启用
)
@EnableConfigurationProperties(MyClientProperties.class)   // 绑定配置属性
public class MyAutoConfiguration {
 
    // @ConditionalOnMissingBean:用户自定义后,默认 Bean 自动退出(覆盖机制)
    @Bean
    @ConditionalOnMissingBean(MyClient.class)
    public MyClient myClient(MyClientProperties properties) {
        return new MyClient(properties);
    }
 
    // 仅在有 DataSource Bean 时才注册(依赖其他 Bean 的条件)
    @Bean
    @ConditionalOnBean(DataSource.class)
    @ConditionalOnMissingBean(MyClientDao.class)
    public MyClientDao myClientDao(MyClient client, DataSource dataSource) {
        return new MyClientDao(client, dataSource);
    }
}

条件注解详见 条件注解,自动配置机制详见 自动配置


步骤四:注册自动配置类

Spring Boot 3.x(推荐)

src/main/resources/META-INF/spring/ 下创建:

org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.example.MyAutoConfiguration

多个配置类每行一个:

com.example.MyAutoConfiguration
com.example.MySecurityAutoConfiguration
com.example.MyMetricsAutoConfiguration

Spring Boot 2.x(兼容写法)

src/main/resources/META-INF/ 下创建:

spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.MyAutoConfiguration,\
  com.example.MySecurityAutoConfiguration

Spring Boot 3.x 仍兼容 spring.factories,但新项目推荐用 .imports 文件。


步骤五:配置元数据提示(IDE 补全)

添加 spring-boot-configuration-processor 依赖,编译时自动生成 additional-spring-configuration-metadata.json,让 IDE 在 application.yml 中提供属性补全和 Javadoc 提示:

<!-- autoconfigure 模块的 pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>   <!-- 不传递给使用方 -->
</dependency>

也可手动编写 META-INF/additional-spring-configuration-metadata.json

{
  "hints": [
    {
      "name": "my.client.server-url",
      "values": [
        { "value": "http://localhost:8088", "description": "本地开发默认地址" },
        { "value": "http://staging-api.example.com", "description": "预发布环境" }
      ]
    }
  ]
}

步骤六:Starter 模块 pom.xml(门面)

<!-- my-starter/pom.xml -->
<project>
    <artifactId>my-spring-boot-starter</artifactId>
 
    <dependencies>
        <!-- 引入 autoconfigure 模块 -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>my-spring-boot-autoconfigure</artifactId>
        </dependency>
        <!-- 其他可选传递依赖 -->
    </dependencies>
</project>

自动配置排序

当多个自动配置类之间有依赖关系时,控制加载顺序:

@AutoConfiguration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)   // 在数据源配置后生效
@AutoConfigureBefore(TransactionAutoConfiguration.class) // 在事务配置前生效
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)           // 最低优先级(最后加载)
public class MyAutoConfiguration {
    ...
}

完整示例:限流 Starter

// 1. 属性类
@ConfigurationProperties(prefix = "rate.limiter")
@Data
public class RateLimiterProperties {
    private boolean enabled = true;
    private int defaultRate = 100;              // 每秒默认 QPS
    private int defaultBurst = 150;
    private Map<String, Integer> apiRates = new HashMap<>();  // 接口级别单独配置
}
 
// 2. 核心拦截器
public class RateLimiterInterceptor implements HandlerInterceptor {
 
    private final RateLimiterProperties properties;
    private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
 
    public RateLimiterInterceptor(RateLimiterProperties properties) {
        this.properties = properties;
    }
 
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) {
        String uri = request.getRequestURI();
        int rate = properties.getApiRates().getOrDefault(uri, properties.getDefaultRate());
        Bucket bucket = buckets.computeIfAbsent(uri, k ->
            Bucket.builder()
                .addLimit(Bandwidth.classic(rate,
                    Refill.greedy(rate, Duration.ofSeconds(1))))
                .build()
        );
        if (bucket.tryConsume(1)) return true;
 
        response.setStatus(429);
        return false;
    }
}
 
// 3. 自动配置类
@AutoConfiguration
@ConditionalOnClass({ HandlerInterceptor.class, Bucket.class })
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnProperty(prefix = "rate.limiter", name = "enabled",
                       havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(RateLimiterProperties.class)
public class RateLimiterAutoConfiguration implements WebMvcConfigurer {
 
    private final RateLimiterProperties properties;
 
    public RateLimiterAutoConfiguration(RateLimiterProperties properties) {
        this.properties = properties;
    }
 
    @Bean
    @ConditionalOnMissingBean
    public RateLimiterInterceptor rateLimiterInterceptor() {
        return new RateLimiterInterceptor(properties);
    }
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(rateLimiterInterceptor())
            .addPathPatterns("/api/**")
            .excludePathPatterns("/actuator/**");
    }
}

使用方只需引入 Starter 并配置:

rate:
  limiter:
    default-rate: 200
    api-rates:
      "/api/orders": 50
      "/api/payments": 20

限流机制详见 限流


测试自动配置

使用 ApplicationContextRunner 在单测中模拟不同条件:

class MyAutoConfigurationTest {
 
    private final ApplicationContextRunner contextRunner =
        new ApplicationContextRunner()
            .withConfiguration(AutoConfigurations.of(MyAutoConfiguration.class));
 
    @Test
    void shouldRegisterBeanByDefault() {
        contextRunner.run(ctx ->
            assertThat(ctx).hasSingleBean(MyClient.class)
        );
    }
 
    @Test
    void shouldNotRegisterWhenDisabled() {
        contextRunner
            .withPropertyValues("my.client.enabled=false")
            .run(ctx ->
                assertThat(ctx).doesNotHaveBean(MyClient.class)
            );
    }
 
    @Test
    void shouldRespectUserDefinedBean() {
        contextRunner
            .withUserConfiguration(UserConfig.class)   // 用户自定义配置
            .run(ctx -> {
                assertThat(ctx).hasSingleBean(MyClient.class);
                assertThat(ctx.getBean(MyClient.class))
                    .isInstanceOf(CustomMyClient.class);  // 使用用户的实现
            });
    }
 
    @Configuration
    static class UserConfig {
        @Bean
        MyClient myClient() {
            return new CustomMyClient();  // 用户覆盖
        }
    }
}

发布与使用

# 发布到内部 Maven 仓库
mvn clean install      # 本地测试
mvn deploy             # 部署到 Nexus / Artifactory

使用方引入:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>my-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>

引入后无需任何额外配置,开箱即用;按需在 application.yml 覆盖默认值即可。


相关链接

  • 自动配置 — Spring Boot 自动配置的加载机制与调试方法
  • 条件注解@ConditionalOnXxx 完整用法
  • 属性绑定@ConfigurationProperties 类型安全绑定
  • 限流 — 限流 Starter 完整实现参考
  • IOC与DI — Bean 注册与覆盖机制