「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」。
1 业务背景
公司是做了很多家医院的微信公众号,每家医院的短信供应商不一样,调用发送验证码的短信平台不一致(腾讯短信、阿里云短信、中国电信短信、中国移动短信……),公司写了很多if...else ,以前的老代码截图如下,脏乱差,而且新增一个短信商很麻烦
2、代码实现说明
这里只对策略模式进行说明,具体实现逻辑用简单的输出语句代替
3 自定义短信类型枚举
import com.ourlang.message.enums.MsgType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* 这里 ElementType.TYPE表示用于类上,
* RetentionPolicy.RUNTIME 表示运行时解析的。
*
* 短信类型的自定义注解
* @author ourlang
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MsgAnnotation {
MsgType type();
}
复制代码
4 创建短信类型的枚举类
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 短信的枚举类
*
* @author ourlang
*/
@AllArgsConstructor
@Getter
public enum MsgType {
/**
* 腾讯短信
*/
TENCENT(1, "腾讯短信"),
/**
* 阿里云短信
*/
ALIBABA(2, "阿里云短信"),
/**
* 中国移动短信
*/
MOBILE(3, "中国移动短信"),
/**
* 中国电信短信
*/
TELECOM(4, "中国电信短信"),
;
/**
* 编码
*/
private final Integer code;
/**
* 说明
*/
private final String desc;
}
复制代码
5 构建抽象策略及策略实现类
import com.ourlang.message.entity.MsgEntity;
/***
* 构建抽象策略及策略实现类
* @author ourlang
*/
public abstract class AbstractMsgStrategy {
/**
* 策略抽象方法 发送短信
* @param msgEntity 短信实体类
* @return 处理成功的返回
*/
abstract public String sendMessage(MsgEntity msgEntity);
}
复制代码
5.1 腾讯短信策略实现类
import com.ourlang.message.annotation.MsgAnnotation;
import com.ourlang.message.entity.MsgEntity;
import com.ourlang.message.enums.MsgType;
import org.springframework.stereotype.Service;
/**
* 腾讯短信策略
* @author ourlang
*/
@Service
@MsgAnnotation( type= MsgType.TENCENT)
public class TencentStrategy extends AbstractMsgStrategy {
@Override
public String sendMessage(MsgEntity msgEntity) {
System.out.println("腾讯短信发送成功"+msgEntity.getPhoneNumber()+msgEntity.getContent()+msgEntity.getVerificationCode());
return "腾讯";
}
}
复制代码
5.2 阿里云策略实现类
import com.ourlang.message.annotation.MsgAnnotation;
import com.ourlang.message.entity.MsgEntity;
import com.ourlang.message.enums.MsgType;
import org.springframework.stereotype.Service;
/**
* 阿里云短信策略
* @author ourlang
*/
@Service
@MsgAnnotation( type= MsgType.ALIBABA)
public class AlibabaStrategy extends AbstractMsgStrategy {
@Override
public String sendMessage(MsgEntity msgEntity) {
System.out.println("阿里云短信发送成功"+msgEntity.getPhoneNumber()+msgEntity.getContent()+msgEntity.getVerificationCode());
return "阿里云";
}
}
复制代码
5.3 中国移动短信策略
import com.ourlang.message.annotation.MsgAnnotation;
import com.ourlang.message.entity.MsgEntity;
import com.ourlang.message.enums.MsgType;
import org.springframework.stereotype.Service;
/**
* 中国移动短信策略
* @author ourlang
*/
@Service
@MsgAnnotation( type= MsgType.MOBILE)
public class MobileStrategy extends AbstractMsgStrategy {
@Override
public String sendMessage(MsgEntity msgEntity) {
System.out.println("中国移动短信发送成功"+msgEntity.getPhoneNumber()+msgEntity.getContent()+msgEntity.getVerificationCode());
return "中国移动";
}
}
复制代码
5.4 中国电信短信策略
import com.ourlang.message.annotation.MsgAnnotation;
import com.ourlang.message.entity.MsgEntity;
import com.ourlang.message.enums.MsgType;
import org.springframework.stereotype.Service;
/**
* 中国电信短信策略
* @author ourlang
*/
@Service
@MsgAnnotation( type= MsgType.TELECOM)
public class TelecomStrategy extends AbstractMsgStrategy {
@Override
public String sendMessage(MsgEntity msgEntity) {
System.out.println("中国电信短信发送成功"+msgEntity.getPhoneNumber()+msgEntity.getContent()+msgEntity.getVerificationCode());
return "中国电信";
}
}
复制代码
6 短信实体类
import com.ourlang.message.enums.MsgType;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 短信实体类
* @author ourlang
*/
@Data
@Accessors(chain = true)
public class MsgEntity {
/**
* 短信平台的唯一主键
*/
private String apiKey;
/**
* 短信秘钥
*/
private String secretKey;
/**
* 电话号码
*/
private String phoneNumber;
/**
* 验证码
*/
private String verificationCode;
/**
* 短信内容
*/
private String content;
/**
* 用于区分短信的类型
*/
private MsgType msgType;
}
复制代码
7 策略分发处理类
import com.ourlang.message.annotation.MsgAnnotation;
import com.ourlang.message.entity.MsgEntity;
import com.ourlang.message.strategy.AbstractMsgStrategy;
import com.ourlang.message.util.SpringBeanUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* 这个类主要是接收业务请求,然后转发到具体的策略类进行处理,
* 这里使用到了spring 获取具体的类,然后通过类上面的注解信息进行转发
*
* @author ourlang
*/
@Service
@Slf4j
public class StrategyUseService {
/**
* 处理取消逻辑
*/
public String sendMessage(MsgEntity msgEntity) {
//代表获取AbstractMsgStrategy 下全部子类或接口。
Map<String, AbstractMsgStrategy> beanMap = SpringBeanUtils.getBeanMap(AbstractMsgStrategy.class);
String message = "";
try {
for (Map.Entry<String, AbstractMsgStrategy> entry : beanMap.entrySet()) {
//代表获取具体的代理类
Object real = SpringBeanUtils.getTarget(entry.getValue());
//代表取类上有MsgAnnotation 的注解信息。
MsgAnnotation annotation = real.getClass().getAnnotation(MsgAnnotation.class);
//通过传递过来的短信类型调用对应的 短信发送类
if (msgEntity.getMsgType().getCode().equals(annotation.type().getCode())) {
message = entry.getValue().sendMessage(msgEntity);
break;
}
}
} catch (Exception e) {
log.error("获取目标代理对象失败{}", e);
message = e.getMessage();
}
return message;
}
}
复制代码
8 动态获取spring管理的bean工具类
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.framework.AopProxy;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.Map;
/**
* 动态获取spring管理的bean实例对象
* spring上下文工具类
* https://github.com/ourlang
*
* @author ourlang
*/
@Component
public class SpringBeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的回调方法,设置上下文环境
*/
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (applicationContext == null) {
applicationContext = context;
}
}
/**
* 获取bean
*
* @param name service注解方式name为小驼峰格式
* @return Object bean的实例对象
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException {
return (T) applicationContext.getBean(name);
}
/**
* 获取bean
*
* @param clz service对应的类
* @return Object bean的实例对象
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(Class<?> clz) throws BeansException {
return (T) applicationContext.getBean(clz);
}
/**
* 获取类型为requiredType的Map
*
* @param clazz
* @return
*/
public static <T> Map<String, T> getBeanMap(Class<T> clazz) {
return applicationContext.getBeansOfType(clazz);
}
/**
* 获取 目标对象
*
* @param proxy 代理对象
* @return 目标对象
* @throws Exception
*/
public static Object getTarget(Object proxy) throws Exception {
if (!AopUtils.isAopProxy(proxy)) {
// 不是代理对象,直接返回
return proxy;
}
if (AopUtils.isJdkDynamicProxy(proxy)) {
return getJdkDynamicProxyTargetObject(proxy);
} else {
// cglib
return getCglibProxyTargetObject(proxy);
}
}
private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
Field field = proxy.getClass().getSuperclass().getDeclaredField("h");
field.setAccessible(true);
AopProxy aopProxy = (AopProxy) field.get(proxy);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();
return target;
}
private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
Field field = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
field.setAccessible(true);
Object dynamicAdvisedInterceptor = field.get(proxy);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
return target;
}
}
复制代码
9 测试
import com.ourlang.message.entity.MsgEntity;
import com.ourlang.message.enums.MsgType;
import com.ourlang.message.service.StrategyUseService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MessageApplicationTests {
@Autowired
private StrategyUseService strategyUseService;
@Test
void contextLoads() {
MsgEntity entity=new MsgEntity();
entity.setContent("你好 apple").setPhoneNumber("13898765432").setVerificationCode("123456").setMsgType(MsgType.ALIBABA);;
String message = strategyUseService.sendMessage(entity);
System.out.println(message);
}
}
复制代码
9.1 输出结果
阿里云短信发送成功13898765432你好 apple123456
阿里云
复制代码
近期评论