本文目录导读:

我来为您详细讲解Spring Boot自定义Starter的完整案例,这个案例将创建一个短信发送的Starter,包含自动配置、属性绑定和条件装配等功能。
项目结构
sms-spring-boot-starter/ ├── pom.xml ├── src/ │ └── main/ │ ├── java/ │ │ └── com/example/sms/ │ │ ├── SmsAutoConfiguration.java │ │ ├── SmsProperties.java │ │ ├── SmsService.java │ │ ├── SmsSender.java │ │ ├── SmsType.java │ │ └── condition/ │ │ └── SmsCondition.java │ └── resources/ │ └── META-INF/ │ └── spring.factories └── ...
创建Starter模块
1 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>sms-spring-boot-starter</artifactId>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
<spring.boot.version>2.7.0</spring.boot.version>
</properties>
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
<!-- Spring Boot Autoconfigure -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
<!-- Configuration Properties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
<optional>true</optional>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<optional>true</optional>
</dependency>
<!-- Gson for JSON -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
2 配置属性类
// SmsProperties.java
package com.example.sms;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* 短信配置属性
*/
@ConfigurationProperties(prefix = "sms")
@EnableConfigurationProperties(SmsProperties.class)
public class SmsProperties {
/**
* 是否启用短信服务
*/
private boolean enabled = true;
/**
* 短信服务提供商
*/
private Provider provider = Provider.ALIYUN;
/**
* 阿里云配置
*/
private AliyunConfig aliyun = new AliyunConfig();
/**
* 腾讯云配置
*/
private TencentConfig tencent = new TencentConfig();
/**
* 短信默认签名
*/
private String signName = "测试签名";
/**
* 短信模板ID
*/
private String templateCode = "SMS_000000";
/**
* 短信服务提供商枚举
*/
public enum Provider {
ALIYUN, TENCENT
}
/**
* 阿里云配置
*/
public static class AliyunConfig {
private String accessKeyId = "your-access-key-id";
private String accessKeySecret = "your-access-key-secret";
private String endpoint = "dysmsapi.aliyuncs.com";
// Getters and Setters
}
/**
* 腾讯云配置
*/
public static class TencentConfig {
private String secretId = "your-secret-id";
private String secretKey = "your-secret-key";
private String region = "ap-guangzhou";
// Getters and Setters
}
// Getters and Setters
}
3 短信接口和实现
// SmsSender.java
package com.example.sms;
/**
* 短信发送接口
*/
public interface SmsSender {
/**
* 发送短信
* @param phone 手机号
* @param content 短信内容
* @return 发送结果
*/
boolean send(String phone, String content);
}
// AliyunSmsSender.java
package com.example.sms;
/**
* 阿里云短信发送实现
*/
public class AliyunSmsSender implements SmsSender {
private final SmsProperties properties;
public AliyunSmsSender(SmsProperties properties) {
this.properties = properties;
}
@Override
public boolean send(String phone, String content) {
// 模拟阿里云短信发送
System.out.println("使用阿里云发送短信");
System.out.println("手机号: " + phone);
System.out.println("内容: " + content);
System.out.println("签名: " + properties.getSignName());
System.out.println("模板: " + properties.getTemplateCode());
System.out.println("AccessKey: " + properties.getAliyun().getAccessKeyId());
// 这里应该调用阿里云SDK发送短信
return true;
}
}
// TencentSmsSender.java
package com.example.sms;
/**
* 腾讯云短信发送实现
*/
public class TencentSmsSender implements SmsSender {
private final SmsProperties properties;
public TencentSmsSender(SmsProperties properties) {
this.properties = properties;
}
@Override
public boolean send(String phone, String content) {
// 模拟腾讯云短信发送
System.out.println("使用腾讯云发送短信");
System.out.println("手机号: " + phone);
System.out.println("内容: " + content);
System.out.println("签名: " + properties.getSignName());
System.out.println("模板: " + properties.getTemplateCode());
System.out.println("SecretId: " + properties.getTencent().getSecretId());
// 这里应该调用腾讯云SDK发送短信
return true;
}
}
4 短信服务门面
// SmsService.java
package com.example.sms;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 短信服务门面
*/
public class SmsService {
private final SmsProperties properties;
private final SmsSender smsSender;
@Autowired
public SmsService(SmsProperties properties, SmsSender smsSender) {
this.properties = properties;
this.smsSender = smsSender;
}
/**
* 发送短信
*/
@SuppressWarnings("unused")
public boolean sendSms(String phone, String content) {
return smsSender.send(phone, content);
}
/**
* 发送验证码
*/
public boolean sendVerificationCode(String phone, String code) {
String content = "您的验证码是:" + code + ",请在10分钟内完成验证。";
return sendSms(phone, content);
}
/**
* 发送营销短信
*/
public boolean sendMarketingMessage(String phone, String content) {
String marketingContent = "【营销】" + content;
return sendSms(phone, marketingContent);
}
/**
* 获取当前短信服务提供商配置
*/
public String getCurrentProviderInfo() {
if (properties.getProvider() == SmsProperties.Provider.ALIYUN) {
return "当前使用阿里云短信服务:AccessKey=" + properties.getAliyun().getAccessKeyId();
} else {
return "当前使用腾讯云短信服务:SecretId=" + properties.getTencent().getSecretId();
}
}
}
5 构造器绑定类
// SmsBuilder.java
package com.example.sms;
/**
* 短信构建器
*/
@SuppressWarnings("unused")
public class SmsBuilder {
private final SmsSender smsSender;
private final SmsProperties properties;
public SmsBuilder(SmsSender smsSender, SmsProperties properties) {
this.smsSender = smsSender;
this.properties = properties;
}
/**
* 构建短信服务
*/
public SmsService build() {
return new SmsService(properties, smsSender);
}
/**
* 构建验证码发送器
*/
public VerificationCodeSender buildVerificationCodeSender(int expireMinutes) {
return new VerificationCodeSender(smsSender, expireMinutes);
}
}
/**
* 验证码发送器
*/
class VerificationCodeSender {
private final SmsSender smsSender;
private final int expireMinutes;
private final Map<String, String> codes = new ConcurrentHashMap<>();
public VerificationCodeSender(SmsSender smsSender, int expireMinutes) {
this.smsSender = smsSender;
this.expireMinutes = expireMinutes;
}
public boolean sendCode(String phone) {
String code = generateCode();
codes.put(phone, code);
return smsSender.send(phone, "您的验证码是:" + code + ",有效期" + expireMinutes + "分钟");
}
public boolean verifyCode(String phone, String inputCode) {
String savedCode = codes.get(phone);
return savedCode != null && savedCode.equals(inputCode);
}
private String generateCode() {
return String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
}
}
6 条件注解
// SmsCondition.java
package com.example.sms.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.StringUtils;
/**
* 短信服务启用条件
*/
public class SmsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String enabled = context.getEnvironment().getProperty("sms.enabled", "true");
if (StringUtils.hasText(enabled)) {
return Boolean.parseBoolean(enabled);
}
return true;
}
}
7 自动配置类
// SmsAutoConfiguration.java
package com.example.sms;
import com.example.sms.condition.SmsCondition;
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;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
/**
* 短信自动配置类
*/
@Configuration
@EnableConfigurationProperties(SmsProperties.class)
@ConditionalOnProperty(prefix = "sms", name = "enabled", havingValue = "true", matchIfMissing = true)
public class SmsAutoConfiguration {
/**
* 创建阿里云短信发送器
*/
@Bean
@ConditionalOnMissingBean
@Conditional(SmsCondition.class)
public SmsSender aliyunSmsSender(SmsProperties properties) {
if (properties.getProvider() == SmsProperties.Provider.ALIYUN) {
return new AliyunSmsSender(properties);
}
return null;
}
/**
* 创建腾讯云短信发送器
*/
@Bean
@ConditionalOnMissingBean
@Conditional(SmsCondition.class)
public SmsSender tencentSmsSender(SmsProperties properties) {
if (properties.getProvider() == SmsProperties.Provider.TENCENT) {
return new TencentSmsSender(properties);
}
return null;
}
/**
* 创建短信服务门面
*/
@Bean
@ConditionalOnMissingBean
public SmsService smsService(SmsProperties properties, SmsSender smsSender) {
return new SmsService(properties, smsSender);
}
/**
* 创建短信构建器
*/
@Bean
@ConditionalOnMissingBean
public SmsBuilder smsBuilder(SmsSender smsSender, SmsProperties properties) {
return new SmsBuilder(smsSender, properties);
}
}
8 自动配置注册
使用 spring.factories(推荐)
# src/main/resources/META-INF/spring.factories org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.sms.SmsAutoConfiguration
使用 AutoConfiguration.imports(Spring Boot 2.7+)
# src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports com.example.sms.SmsAutoConfiguration
创建使用示例项目
1 示例项目pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>sms-starter-demo</artifactId>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 我们的自定义Starter -->
<dependency>
<groupId>com.example</groupId>
<artifactId>sms-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2 应用配置
# application.yml
server:
port: 8080
sms:
enabled: true
provider: ALIYUN # 或 TENCENT
sign-name: 测试签名
template-code: SMS_123456
aliyun:
access-key-id: LTAI5tXXXXXXX
access-key-secret: your-secret
endpoint: dysmsapi.aliyuncs.com
tencent:
secret-id: AKIDXXXX
secret-key: your-secret-key
region: ap-guangzhou
3 应用启动类和控制器
// Application.java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// 获取短信服务并测试
SmsService smsService = context.getBean(SmsService.class);
System.out.println(smsService.getCurrentProviderInfo());
}
}
// SmsController.java
package com.example.demo.controller;
import com.example.sms.SmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* 短信控制层
*/
@RestController
@RequestMapping("/api/sms")
public class SmsController {
@Autowired
private SmsService smsService;
/**
* 发送短信
*/
@PostMapping("/send")
public String sendSms(@RequestParam String phone, @RequestParam String content) {
boolean result = smsService.sendSms(phone, content);
return result ? "发送成功" : "发送失败";
}
/**
* 发送验证码
*/
@PostMapping("/verification")
public String sendVerificationCode(@RequestParam String phone) {
String code = generateCode();
boolean result = smsService.sendVerificationCode(phone, code);
return result ? "验证码发送成功: " + code : "发送失败";
}
/**
* 发送营销短信
*/
@PostMapping("/marketing")
public String sendMarketing(@RequestParam String phone, @RequestParam String content) {
boolean result = smsService.sendMarketingMessage(phone, content);
return result ? "营销短信发送成功" : "发送失败";
}
/**
* 获取当前配置信息
*/
@GetMapping("/config")
public String getConfig() {
return smsService.getCurrentProviderInfo();
}
private String generateCode() {
return String.valueOf((int) ((Math.random() * 9 + 1) * 100000));
}
}
4 高级用法示例
// SmsBuilderDemo.java
package com.example.demo;
import com.example.sms.SmsBuilder;
import com.example.sms.SmsService;
import com.example.sms.VerificationCodeSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class SmsBuilderDemo {
@Autowired
private SmsBuilder smsBuilder;
public void demoBuilder() {
// 构建自定义验证码发送器
VerificationCodeSender codeSender = smsBuilder.buildVerificationCodeSender(5);
// 发送验证码
boolean sent = codeSender.sendCode("13800138000");
// 验证验证码
boolean verified = codeSender.verifyCode("13800138000", "123456");
}
public void demoSmsService() {
// 通过SmsService直接发送
SmsService smsService = smsBuilder.build();
smsService.sendSms("13800138000", "Hello Spring Boot Starter!");
}
}
Starter优化选项
1 添加监控端点
// SmsHealthIndicator.java
package com.example.sms.actuator;
import com.example.sms.SmsService;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
/**
* 短信服务健康检查
*/
@Component
public class SmsHealthIndicator implements HealthIndicator {
private final SmsService smsService;
public SmsHealthIndicator(SmsService smsService) {
this.smsService = smsService;
}
@Override
public Health health() {
try {
// 模拟检查短信服务
String info = smsService.getCurrentProviderInfo();
return Health.up()
.withDetail("smsProvider", info)
.withDetail("status", "短信服务运行正常")
.build();
} catch (Exception e) {
return Health.down(e).withDetail("smsProvider", "短信服务不可用").build();
}
}
}
2 支持多配置源
// SmsConfigMerger.java
package com.example.sms.support;
import com.example.sms.SmsProperties;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* 配置合并器
*/
@Component
public class SmsConfigMerger {
@Value("${sms.provider:ALIYUN}")
private String provider;
/**
* 合并配置优先级:命令行 > 配置文件 > 默认配置
*/
public SmsProperties mergeConfig(SmsProperties properties) {
// 可以根据实际需求实现复杂的配置合并逻辑
return properties;
}
}
实际使用验证
1 编写单元测试
// SmsServiceTest.java
package com.example.demo;
import com.example.sms.SmsService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertTrue;
@SpringBootTest
public class SmsServiceTest {
@Autowired
private SmsService smsService;
@Test
public void testSendSms() {
boolean result = smsService.sendSms("13800138000", "Test message");
assertTrue(result);
}
@Test
public void testSendVerificationCode() {
boolean result = smsService.sendVerificationCode("13800138000", "123456");
assertTrue(result);
}
@Test
public void testGetProviderInfo() {
String info = smsService.getCurrentProviderInfo();
System.out.println(info);
assertTrue(!info.isEmpty());
}
}
2 使用curl测试
# 发送普通短信 curl -X POST "http://localhost:8080/api/sms/send?phone=13800138000&content=Hello" # 发送验证码 curl -X POST "http://localhost:8080/api/sms/verification?phone=13800138000" # 发送营销短信 curl -X POST "http://localhost:8080/api/sms/marketing?phone=13800138000&content=促销活动" # 获取配置信息 curl GET "http://localhost:8080/api/sms/config"
部署和安装Starter
1 安装到本地Maven仓库
cd sms-spring-boot-starter mvn clean install
2 部署到远程仓库
在pom.xml中添加部署配置:
<distributionManagement>
<repository>
<id>releases</id>
<url>http://your-nexus/repository/maven-releases/</url>
</repository>
<snapshotRepository>
<id>snapshots</id>
<url>http://your-nexus/repository/maven-snapshots/</url>
</snapshotRepository>
</distributionManagement>
然后执行:
mvn deploy
1 Starter的关键部分
- 自动配置类:提供自动配置逻辑
- 属性绑定类:读取配置并绑定到Java对象
- 条件注解:控制配置何时生效
- 服务实现:提供业务功能实现
- spring.factories:注册自动配置类
2 设计原则
- 命名规范:
xxx-spring-boot-starter - 条件装配:使用
@ConditionalOn*注解 - 属性设置:使用
@ConfigurationProperties - 自动配置:通过
@EnableAutoConfiguration自动生效 - 可选依赖:使用
optional标记依赖 - 简化使用:让使用者通过极少的配置即可使用
3 使用建议
- 配置属性要有默认值
- 提供必要的校验逻辑
- 支持自动配置和手动配置的切换
- 提供良好的文档和示例
- 做好向后兼容性
这个完整的Starter案例展示了如何创建一个功能完整、可配置、易用的Spring Boot Starter,包含了实际项目中常用的各种模式和最佳实践。