学以致用之NamespaceHandlerSupport

前言

看源码这事,也就一个兴趣而已。工作阅历随着时间增长,回望以前写的代码,粗糙而又简陋。刚入猿星人这行时,基本是对着被谩骂许久的《java从入门到精通》抄的。最近有看Spring 4.3.3的源码,正好做项目中有提到要优化MQ相关逻辑的意见。现如今就对着Spring的NamespaceHandlerSupport设计思路,抄它一抄。

项目需求背景

1.MQ使用的是aliyun提供的消息队列,底层为RocketMQ,传输消息类型为byte

2.实现的代码结构如下,消费Message时,根据Message的Topic和Tag分别进行相关的处理,这里用的是if-else和switch

public class AliyunMQListener implements MessageListener {
    private static final Logger logger = LoggerFactory.getLogger(AliyunMQListener.class);

    @Resource
    private MQConsumerService mqConsumerService;

    @Override
    public Action consume(Message message, ConsumeContext consumeContext) {
        try {
            String data = new String(message.getBody(), "UTF-8");
            logger.info("接收到消息,{}", data);
            if (message.getTopic().equals(SystemParamInit.getMQTopic())) {
                switch (message.getTag()) {
                    case CommonValue.TOPIC_TAG:
                        mqConsumerService.checkSensitiveWord4Topic(JsonUtils.toBean(data, TopicContent.class));
                        break;
                    case CommonValue.COMMENT_TAG:
                        mqConsumerService.checkSensitiveWord4Comment(JsonUtils.toBean(data, CommentContent.class));
                        break;
                    case CommonValue.TOPIC_CREATOR_BANNED_TAG:
                        mqConsumerService.updateUserTopic(JsonUtils.toBean(data, TopicCreatorBanned.class));
                        break;
                }
            }
        } catch (Exception e) {
            logger.error("消息消费失败,{}", e);
            return Action.ReconsumeLater;
        }
        return Action.CommitMessage;
    }
}
3.有同事提出,上面的实现方式不易维护,每多一个tag得加个case,每多一个topic得加一个if-else 这样做很蠢。

解决思路

Spring的自定义标签解析是通过,写一个继承自NamespaceHandlerSupport的类,并实现init()方法,在init()方法中,去注册解析器。然后在解析xml时,通过约定的key去Map中拿到相应的解析器进行解析。大致思路有了,就开始对上面的逻辑进行改造。对应的设计模式为:接口-适配器模式、抽象工厂模式、策略模式及模板方法模式

首先,我们需要定义一个消息解析器接口,解析器的实现就是对相应tag的消息的处理

public interface IMessageParser<T> {
    JmsAction parse(T message);
}
然后,定义一个消息处理器接口,包含初始化方法、获取路径及接受Message实现消息分发的逻辑

public interface IMessageHandler<T> {
    void init();

    String getDestination(T message);

    JmsAction parse(T message);
}
接着,定义消息处理器的抽象类。形如NamespaceHandlerSupport。这边要实现消息的分发和解析器的注册

public abstract class MessageHandlerSupport<T> implements IMessageHandler<T> {

    private final Map<String, IMessageParser> parsers = new ConcurrentHashMap<>();

    @Override
    public JmsAction parse(T message) {
        return findParserForMessage(message).parse(message);
    }

    private IMessageParser findParserForMessage(T message) {
        IMessageParser parser = this.parsers.get(getDestination(message));
        if (parser == null) {
            throw new MessageParserException("No MessageParser is matched,Destination is " + getDestination(message));
        }
        return parser;
    }

    protected final void registerMessageParser(String elementName, IMessageParser parser) {
        this.parsers.put(elementName, parser);
    }

}
之后,定义一个处理器容器注册类。在Spring容器启动的时候,需要进行初始化,将需要的消息处理器放入其中,顺带提供个选择处理器的方法

public class MessageHandlerRegister {
    private Map<String, IMessageHandler> container = new ConcurrentHashMap<>();

    public Map<String, IMessageHandler> getContainer() {
        return container;
    }

    public void setContainer(Map<String, IMessageHandler> container) {
        this.container = container;
    }

    public IMessageHandler findMessageHandler(String topicName) {
        if (CollectionUtils.isEmpty(container)) {
            return null;
        } else {
            return container.get(topicName);
        }
    }
}
最后,再对原来的Listener进行调整
public class AliyunMQListener implements MessageListener {

    private static final Logger logger = LoggerFactory.getLogger(AliyunMQListener.class);

    @Resource
    private MessageHandlerRegister messageHandlerRegister;

    @Override
    public Action consume(Message message, ConsumeContext context) {
        IMessageHandler messageHandler = messageHandlerRegister.findMessageHandler(message.getTopic());
        if (null == messageHandler) {
            logger.warn("No MessageHandler is matched,topic is {}", message.getTopic());
        } else {
            messageHandler.parse(message);
        }
        return Action.CommitMessage;
    }

}
这时候,这代码就简洁多了。而且如果需要增加处理新的topic和tag,只要添加新的处理器和解析器然后进行注册。

后记

aliyun的listener中有个ConsumeContext对象。

package com.aliyun.openservices.ons.api;

/**
 * 每次消费消息的上下文,供将来扩展使用
 */
public class ConsumeContext {

}
感觉么,consume(Message message, ConsumeContext context)这种格式和Spring里的parse(Element element, ParserContext parserContext)有点像。。

最后附上activemq和rabbitmq的测试代码。https://github.com/Boneix1992/Demos/tree/master/base-core/base-jms





<p> <span style="font-size:16px;color:#E53333;"><strong>内容简介:</strong></span> </p> <p> <span style="font-size:16px;">本课程主要是从最基础的技术要点一步一个脚印的介绍Spring Boot2.0相关的核心技术栈,包括如何基于Spring Boot2.0搭建一个企业级的多模块项目、整合Spring MVC和Mybatis实现项目中功能模块的CRUD,校验器Validator、Lombok、动态配置参数、开发环境切换、全局异常处理、定时任务、多线程、热加载、发送邮件、上传下载文件、多数据源等核心干货,为各位小伙伴提供企业级项目开发中常见且典型的核心技术!</span> </p> <p> <br /> </p> <p> <span style="font-size:16px;color:#E53333;"><strong>其课程大纲如下所示:</strong></span> </p> <p> <img src="https://img-bss.csdn.net/202004070546409819.jpg" alt="" /><span style="font-size:16px;"></span> </p> <p> <br /> </p> <p> <br /> </p> <p> <br /> </p> <p> <br /> </p> <p> <br /> </p> <p> <span style="font-size:16px;">在课程的最后,我们介绍并实战了一个典型的应用场景:“<span style="font-size:16px;">用户注册</span>”,主要是将前面章节介绍的内容融入到一个完整的功能模块中,做到学以致用!</span><span style="font-size:16px;">其完整的功能流程图如下图所示:</span> </p> <p> <img src="https://img-bss.csdn.net/202004070548344015.png" alt="" /> </p> <p> <span style="font-size:16px;">基本要求:</span> </p> <span style="font-size:16px;"></span> <p> <br /> </p> <p> <br /> </p> <p> <span style="font-size:16px;">1、</span><span style="font-size:16px;">基本要求:具备一定的</span><span style="font-size:16px;">JavaSE</span><span style="font-size:16px;">以及</span><span style="font-size:16px;">Java Web</span><span style="font-size:16px;">项目的开发基础、了解</span><span style="font-size:16px;">spring boot</span><span style="font-size:16px;">更佳</span><span></span> </p> <p> <span style="font-size:16px;">2、</span><span style="font-size:16px;">工具要求:会使用</span><span style="font-size:16px;">Intellij IDEA </span><span style="font-size:16px;">、</span><span style="font-size:16px;">Navicat </span><span style="font-size:16px;">以及</span><span style="font-size:16px;"> Postman</span> </p>
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页