Spring几种使用策略设计模式的方式

同学们可以看看,项目中是不是有这样类似的代码

问题不是很大,但是可能经常要改,只能一个小哥维护

    public static void sendHttpRequest(HttpMethod httpMethod, Map<String, Object> paramsMap) {
        switch (httpMethod) {
            case GET:
                //doGet(paramsMap);
            case PUT:
                //doPut(paramsMap);
            case HEAD:
                //doHead(paramsMap);
            case POST:
                //doPost(paramsMap);
            case PATCH:
                //doPatch(paramsMap);
            case TRACE:
                //doTrace(paramsMap);
            case DELETE:
                //doDelete(paramsMap);
            case OPTIONS:
                //doOptions(paramsMap);
        }
    }

废话不多说直接上代码

先定义一个接口

public interface HttpMethodHandler {

  void sendRequest(Map<String, Object> map);
  
}

为get请求写一个实现类

@Component
public class GetMethodHandler implements HttpMethodHandler{

  @Override
  public void sendRequest(Map<String, Object> map) {
    doGet(map);    
  }
}

然后修改一下调用的方法,只需要传入一个处理器的clazz即可

public static void sendHttpRequest(Class<? extends HttpMethodHandler> clazz, Map<String, Object> paramsMap) {
  MethodHandler handler = SpringUtil.getBean(clazz);
  if(handler == null){
    throw new RuntimeException("no such http hanlder");
  }
  handler.sendRequest(paramsMap);

这是一个比较简单的基于Spring的策略设计模式的实现.

有个小问题,那就是传入clazz这种方式只有开发的人比较熟悉,有什么什么实现类可以干什么什么事情,如果是我们日常工作的时候需要其他小伙伴调用的话,会有一些理解上的出入,一般情况下,我们使用枚举比较多.

/**
* 发送http请求
*
* @param httpMethod HTTP方法
* @param paramsMap  参数贴图
*/
public void sendHttpRequest(HttpMethod httpMethod, Map<String, Object> paramsMap) {
}

在枚举文件稍加文档说明就可以把事情说清楚

image-20210929140457271

所以我们需要将枚举和策略实现类进行一个绑定,可以使用这种简单粗暴的方式,问题也不是很大,多一个策略就多一个枚举值,没毛病

public enum HttpMethod {

  GET(GetMethodHandler.class),

  POST(PostMethodHandler.class);

  public Class<? extends HttpMethodHandler> getClazz() {
    return clazz;
  }

  private final Class<? extends HttpMethodHandler> clazz;

  HttpMethod(Class<? extends HttpMethodHandler> clazz) {
    this.clazz = clazz;
  }
}

代码如下,使用SpringUtil通过class获取Bean

 public static void sendHttpRequest(HttpMethod httpMethod, Map<String, Object> paramsMap) {
   	  HttpMethodHandler handler = SpringUtil.getBean(httpMethod.getClazz());
      handler.sendRequest(paramsMap);
 }

实际上我们项目中,使用的另一种方法: 注解 + 枚举

@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpMethodStrategy {
    
    HttpMethod value();
}

实现类添加我们定义的注解,并指定类型

@Component
@HttpMethodStrategy(HttpMethod.GET)
public class GetMethodHandler implements HttpMethodHandler {

    @Override
    public void sendRequest(Map<String, Object> map) {
        //doGet(map);
    }
}

写一个容器Map,将策略都塞进Map里,key是枚举,value是Bean.

简单说明一下,首先我们通过getBeanByAnnotation()方法获取方法上含有指定注解的Bean,这些Bean可能并没有实现HttpMethodHandler接口,这里需要我们开发自己保证.

然后我们将HttpMethod做为key,Handler处理器方法作为value,这样我们就做好一个枚举转换成策略实现类的一个工具类

@Component
public class HttpMethodContext implements ApplicationListener<ContextRefreshedEvent> {

    private static final Map<HttpMethod, HttpMethodHandler> handlerMap = new EnumMap<HttpMethod, HttpMethodHandler>(HttpMethod.class);

    public static HttpMethodHandler getHandler(HttpMethod httpMethod) {
        return handlerMap.get(httpMethod);
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        Map<String, Object> beans = contextRefreshedEvent.getApplicationContext().getBeansWithAnnotation(HttpMethodStrategy.class);
        beans.forEach((key, bean) -> {
            HttpMethodStrategy strategy = bean.getClass().getAnnotation(HttpMethodStrategy.class);
            HttpMethod httpMethod = strategy.value();
            handlerMap.put(httpMethod, (HttpMethodHandler) bean);
        });
    }
}

我们完善一下工具类的代码

/**
* 发送http请求
*
* @param httpMethod HTTP方法
* @param paramsMap  参数贴图
*/
public void sendHttpRequest(HttpMethod httpMethod, Map<String, Object> paramsMap) {
  	HttpMethodHandler handler = HttpMethodContext.getHanlder(httpMethod);
    handler.sendRequest(paramsMap);
}

简单的说,我们将一个枚举来映射一个Bean,来实现策略设计模式的.

我再举几个项目中使用到的例子.

MQ消费

比如我们系统收到一个MQ消息,我们已经得到了topi,tag和message内容,我们现在要进行消费,随着业务的发展,这些消息的数量越来越多,需要我们不断地去拓展消息类型.

先定义一个消息消费者

public interface RocketMQMsgConsumer {

  default boolean match(String topic,String tag){
    return false;
  }

  default boolean consume(String message){
    return false;
  }
}

写一个消费者存储列表

@Component
public class RocketMQMsgConsumerContext implements ApplicationListener<ContextRefreshedEvent> {


    private static final List<RocketMqMsgConsumer> CONSUMERS = new ArrayList<>();

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        Map<String, RocketMqMsgConsumer> beanMap = event.getApplicationContext().getBeansOfType(RocketMqMsgConsumer.class);
        CONSUMERS.addAll(beanMap.values());
    }
    
    public List<RocketMqMsgConsumer> getConsumers(String topic,String tag){
        return CONSUMERS.stream()
                .filter(item -> item.match(topic,tag))
                .collect(Collectors.toList());
    }
}

mq消费监听器

这里我们的消费者可能是多个,只有全部成功才能成功,否则会提示RocketMQ等下再发消息过来.大家可以也可以设置一个consumer

@Component
public class MessageConsumerListener implements MessageListener {

    @Override
    public Action consume(Message message, ConsumeContext context) {
        
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);

        List<RocketMqMsgConsumer> consumers = RocketMQMsgConsumerContext.getConsumers(message.getTopic(), message.getTag());
        
        boolean result = true;

        for (RocketMqMsgConsumer consumer : consumers) {
            result &= consumer.consume(msg);
        }
        return result ? Action.CommitMessage : Action.ReconsumeLater;
    }
}

其他例子,也有很多,讲一下可拓展的点

  • 像前端在页面上配置了某个配置项(这个配置项下拉框列表可以直接给Enum.values()),然后存储到后台数据库,假设存储的值为 1,2,3,4,5 ,然后我们取出这个配置值,再用我们的context获取一个策略类,然后执行其中的方法.这样我们后端只需要定义策略类的类型,并进行一个实现,需要加一个策略就多一个枚举值,再写一个策略实现方法.
  • 策略方法之间还可以使用一些继承,比如都继承一个abstractHandler,之类的,拓展性拉满
  • getHandler()这个方法如果找不到对应的策略类还可以整一个默认的DefaultHandler,简直无敌啊.
  • 在我们的接口上多写一些注解和参数说明,其他人也可以一起使用,一起写一个实现类,但是要注意事项说清楚.
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值