一、引言
在现代的软件开发中,短信服务是一个常见的需求,比如验证码发送、通知提醒等。不同的业务场景可能会选择不同的第三方短信平台,如阿里云、腾讯云、亚马逊云等。为了方便开发者在 Spring Boot 项目中灵活使用这些短信服务,我们可以自定义一个 Spring Boot Starter 来封装短信服务的逻辑。
本文将详细介绍如何在 Spring Boot 3.x 中自定义封装一个 sms-spring-boot-starter,让开发者可以通过配置灵活选择短信服务提供商,并且默认使用腾讯云作为短信服务商。
二、核心架构设计
2.1 整体架构
我们的 Starter 采用工厂模式和策略模式,实现多服务商的动态切换:
从客户端请求到短信发送的整个流程
2.2 项目结构
我们的项目包含两个主要模块:
ssm-spring-boot-starter
自定义的 Starter 模块,封装短信服务的核心逻辑。
starter-test
测试模块,用于验证 Starter 的功能。
ssm-spring-boot-starter 模块结构
ssm-spring-boot-starter
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── fox
│ │ │ └── ssmspringbootstarter
│ │ │ ├── config
│ │ │ │ ├── SmsAutoConfiguration.java
│ │ │ │ ├── SmsProperties.java
│ │ │ │ └── SmsTemplate.java
│ │ │ ├── constant
│ │ │ │ └── SmsTypeEnum.java
│ │ │ ├── factory
│ │ │ │ └── SmsHandleFactory.java
│ │ │ └── service
│ │ │ ├── SmsService.java
│ │ │ └── impl
│ │ │ ├── AliCloudSmsServiceImpl.java
│ │ │ ├── TxCloudSmsServiceImpl.java
│ │ │ └── YmxCloudSmsServiceImpl.java
│ │ └── resources
│ │ └── META-INF
│ │ └── spring
│ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── pom.xml
starter-test 模块结构
starter-test
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── fox
│ │ │ └── startertest
│ │ │ ├── StarterTestApplication.java
│ │ │ └── controller
│ │ │ └── SmsController.java
│ │ └── resources
│ │ └── application.properties
│ └── test
│ └── java
│ └── com
│ └── fox
│ └── startertest
│ └── StarterTestApplicationTests.java
└── pom.xml
三、代码实现
3.1 定义短信服务类型枚举
在 ssm-spring-boot-starter 模块中,我们首先定义一个枚举类 SmsTypeEnum 来表示不同的短信服务提供商:
package com.fox.ssmspringbootstarter.constant;
/**
* @author Fox
*/
public enum SmsTypeEnum {
// 阿里云
ALI_CLOUD("ali"),
// 腾讯云
TX_CLOUD("tx"),
// 亚马逊云
YMX_CLOUD("ymx");
private String type;
SmsTypeEnum(String type) {
this.type = type;
}
public String getType() {
return type;
}
}
3.2 定义短信服务接口和实现类
定义一个 SmsService 接口,包含发送短信的方法:
package com.fox.ssmspringbootstarter.service;
/**
* @author Fox
*/
public interface SmsService {
/**
* 发送短信
*
* @param fromPhone 发送方手机号
* @param toPhone 接收方手机号
* @param content 短信内容
* @return 发送结果
*/
String send(String fromPhone, String toPhone, String content);
}
然后为每个短信服务提供商实现该接口:
// 阿里云短信服务实现类
package com.fox.ssmspringbootstarter.service.impl;
import com.fox.ssmspringbootstarter.service.SmsService;
import org.springframework.stereotype.Service;
/**
* 阿里云 SMS 实现
*
* @author Fox
*/
@Service("ali")
public class AliCloudSmsServiceImpl implements SmsService {
@Override
public String send(String fromPhone, String toPhone, String content) {
System.out.println("------------------当前 SMS 厂商为阿里云------------------");
System.out.println("----" + fromPhone + " 向 " + toPhone + " 发送了一条短信。" + "----");
System.out.println("短信内容为:" + content);
System.out.println("----------------------------------------------------");
return "success";
}
}
// 腾讯云短信服务实现类
package com.fox.ssmspringbootstarter.service.impl;
import com.fox.ssmspringbootstarter.service.SmsService;
import org.springframework.stereotype.Service;
/**
* 腾讯云 SMS 实现
*
* @author Fox
*/
@Service("tx")
public class TxCloudSmsServiceImpl implements SmsService {
@Override
public String send(String fromPhone, String toPhone, String content) {
System.out.println("------------------当前 SMS 厂商为腾讯云------------------");
System.out.println("----" + fromPhone + " 向 " + toPhone + " 发送了一条短信。" + "----");
System.out.println("短信内容为:" + content);
System.out.println("----------------------------------------------------");
return "success";
}
}
// 亚马逊云短信服务实现类
package com.fox.ssmspringbootstarter.service.impl;
import com.fox.ssmspringbootstarter.service.SmsService;
import org.springframework.stereotype.Service;
/**
* 亚马逊云 SMS 实现
*
* @author Fox
*/
@Service("ymx")
public class YmxCloudSmsServiceImpl implements SmsService {
@Override
public String send(String fromPhone, String toPhone, String content) {
System.out.println("------------------当前 SMS 厂商为亚马逊云------------------");
System.out.println("----" + fromPhone + " 向 " + toPhone + " 发送了一条短信。" + "----");
System.out.println("短信内容为:" + content);
System.out.println("----------------------------------------------------");
return "success";
}
}
3.3 定义短信服务工厂类
创建一个 SmsHandleFactory 类,用于根据配置动态创建短信服务实例:
package com.fox.ssmspringbootstarter.factory;
import com.fox.ssmspringbootstarter.service.SmsService;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class SmsHandleFactory {
@Autowired
private ListableBeanFactory beanFactory; // 注入 Spring 容器
/**
* 创建处理类对象
*
* @param code 短信服务提供商代码
* @return SmsService 实例
*/
public SmsService createSmsService(String code) {
// 从 Spring 容器中按名称获取 Bean(code 需与 @Service("code") 一致)
return beanFactory.getBean(code, SmsService.class);
}
}
3.4 定义配置类和属性类
创建 SmsProperties 类来读取配置文件中的短信服务提供商配置:
package com.fox.ssmspringbootstarter.config;
import com.fox.ssmspringbootstarter.constant.SmsTypeEnum;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Fox
*/
@ConfigurationProperties(prefix = "sms.server")
public class SmsProperties {
/**
* 发送短信类型
*/
private String type;
public String getType() {
if (type == null || "".equals(type)) {
type = SmsTypeEnum.TX_CLOUD.getType();
}
return type;
}
public void setType(String type) {
this.type = type;
}
}
创建 SmsAutoConfiguration 类来自动配置短信服务:
package com.fox.ssmspringbootstarter.config;
import com.fox.ssmspringbootstarter.factory.SmsHandleFactory;
import com.fox.ssmspringbootstarter.service.SmsService;
import com.fox.ssmspringbootstarter.constant.SmsTypeEnum;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import java.util.List;
/**
* @author Fox
*/
@AutoConfiguration
@ConditionalOnClass({SmsTemplate.class})
@EnableConfigurationProperties(value = SmsProperties.class)
public class SmsAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SmsTemplate smsTemplate() {
return new SmsTemplate();
}
}
自动配置类上使用了几个关键的条件注解:
-
@AutoConfiguration
这是 Spring Boot 3.x 新引入的注解,专门用于标记自动配置类。它替代了 Spring Boot 2.x 中的 @Configuration 注解,使自动配置类的语义更加明确。 -
@ConditionalOnClass({SmsTemplate.class})
这个注解表示只有当类路径中存在 SmsTemplate 类时,才会加载这个自动配置类。这确保了只有在引入了我们的 Starter 依赖时,自动配置才会生效。 -
@EnableConfigurationProperties(value = SmsProperties.class)
这个注解启用了 SmsProperties 类的配置属性绑定功能,允许我们通过 application.properties 或 application.yml 配置短信服务参数。
创建Spring Boot 3.x 自动配置文件
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.fox.ssmspringbootstarter.config.SmsAutoConfiguration
如果需要同时兼容 Spring Boot 2.x 和 3.x,可以保留两种配置文件:
ssm-spring-boot-starter
└── src
└── main
└── resources
├── META-INF
│ ├── spring
│ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports # Spring Boot 3.x
│ └── spring.factories # Spring Boot 2.x 兼容
其中,spring.factories 内容为:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.fox.ssmspringbootstarter.config.SmsAutoConfiguration
创建 SmsTemplate 类来封装短信发送逻辑:
package com.fox.ssmspringbootstarter.config;
import com.fox.ssmspringbootstarter.factory.SmsHandleFactory;
import com.fox.ssmspringbootstarter.service.SmsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author Fox
*/
@Component
public class SmsTemplate {
@Autowired
private SmsProperties smsProperties;
@Autowired
private SmsHandleFactory smsHandleFactory;
public String send(String fromPhone, String toPhone, String content) {
// 获取云厂商的业务实现类
String type = smsProperties.getType();
SmsService smsService = smsHandleFactory.createSmsService(type);
if (smsService == null) {
throw new IllegalArgumentException("No SmsService implementation found for type: " + type);
}
return smsService.send(fromPhone, toPhone, content);
}
}
当开发者在应用中引入我们的 sms-spring-boot-starter 依赖后,整个自动配置流程如下:
- 应用启动:Spring Boot 应用启动时,会自动扫描类路径下的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件。
- 加载自动配置类:发现并加载我们的 SmsAutoConfiguration 类。
- 条件检查:检查是否存在 SmsTemplate 类(通过 @ConditionalOnClass),确保 Starter 被正确引入。
- 配置属性绑定:将配置文件中以 sms.server 为前缀的属性绑定到 SmsProperties 类的字段上。
- 创建 Bean:创建 SmsTemplate 和 SmsHandleFactory Bean。
- 服务发现:SmsHandleFactory 从 Spring 容器中获取所有实现了 SmsService 接口的 Bean。
- 使用服务:开发者注入 SmsTemplate 并调用 send 方法时,SmsTemplate 根据配置选择合适的服务实现并调用其发送方法。
3.5 测试模块
在 starter-test 模块中,创建一个 SmsController 来测试短信发送功能:
package com.fox.startertest.controller;
import com.fox.ssmspringbootstarter.config.SmsTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author Fox
*/
@RestController
@RequestMapping("/sms")
public class SmsController {
@Resource
private SmsTemplate smsTemplate;
@RequestMapping("/send")
public String send() {
String fromPhone = "186xxxxxxxx";
String toPhone = "156xxxxxxxx";
String content = "Fox,今晚十点王者 五缺一,收到请回复,over!";
return smsTemplate.send(fromPhone, toPhone, content);
}
}
在 application.properties 中配置短信服务提供商:
spring.application.name=starter-test
sms.server.type=tx
启动服务后测试:http://localhost:8080/sms/send,控制台输出
四、总结
通过自定义 Spring Boot Starter,我们实现了一个灵活的短信服务封装,开发者可以通过配置轻松切换不同的短信服务提供商。这种方式提高了代码的复用性和可维护性,使得项目在不同的业务场景下能够快速适配不同的短信服务。同时,利用 Spring Boot 的自动配置功能,简化了开发者的使用步骤,让开发者可以更专注于业务逻辑的实现。