自定义 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),但官方推荐分离以保持职责清晰。
命名规范
| 类型 | 命名格式 | 示例 |
|---|---|---|
| 官方 Starter | spring-boot-starter-{name} | spring-boot-starter-web |
| 第三方 / 自定义 | {name}-spring-boot-starter | mybatis-spring-boot-starter |
| autoconfigure 模块 | {name}-spring-boot-autoconfigure | mybatis-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.MySecurityAutoConfigurationSpring 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 覆盖默认值即可。