学习java策略模式(注解)

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」。

1 业务背景

公司是做了很多家医院的微信公众号,每家医院的短信供应商不一样,调用发送验证码的短信平台不一致(腾讯短信、阿里云短信、中国电信短信、中国移动短信……),公司写了很多if...else ,以前的老代码截图如下,脏乱差,而且新增一个短信商很麻烦

image.png

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
阿里云
复制代码

10 完整代码

gitee.com/ourlang/mes…