Spring Boot自定义Starter案例

wen java案例 2

本文目录导读:

Spring Boot自定义Starter案例

  1. 项目结构
  2. 创建Starter模块
  3. 创建使用示例项目
  4. Starter优化选项
  5. 实际使用验证
  6. 部署和安装Starter

我来为您详细讲解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的关键部分

  1. 自动配置类:提供自动配置逻辑
  2. 属性绑定类:读取配置并绑定到Java对象
  3. 条件注解:控制配置何时生效
  4. 服务实现:提供业务功能实现
  5. spring.factories:注册自动配置类

2 设计原则

  1. 命名规范xxx-spring-boot-starter
  2. 条件装配:使用@ConditionalOn*注解
  3. 属性设置:使用@ConfigurationProperties
  4. 自动配置:通过@EnableAutoConfiguration自动生效
  5. 可选依赖:使用optional标记依赖
  6. 简化使用:让使用者通过极少的配置即可使用

3 使用建议

  1. 配置属性要有默认值
  2. 提供必要的校验逻辑
  3. 支持自动配置和手动配置的切换
  4. 提供良好的文档和示例
  5. 做好向后兼容性

这个完整的Starter案例展示了如何创建一个功能完整、可配置、易用的Spring Boot Starter,包含了实际项目中常用的各种模式和最佳实践。

抱歉,评论功能暂时关闭!