学以致用之NamespaceHandlerSupport 学以致用之NamespaceHandlerSupport

https://blog.csdn.net/boneix/article/details/73608573


学以致用之NamespaceHandlerSupport

前言

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

项目需求背景

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

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

[java]  view plain  copy
  1. public class AliyunMQListener implements MessageListener {  
  2.     private static final Logger logger = LoggerFactory.getLogger(AliyunMQListener.class);  
  3.   
  4.     @Resource  
  5.     private MQConsumerService mqConsumerService;  
  6.   
  7.     @Override  
  8.     public Action consume(Message message, ConsumeContext consumeContext) {  
  9.         try {  
  10.             String data = new String(message.getBody(), "UTF-8");  
  11.             logger.info("接收到消息,{}", data);  
  12.             if (message.getTopic().equals(SystemParamInit.getMQTopic())) {  
  13.                 switch (message.getTag()) {  
  14.                     case CommonValue.TOPIC_TAG:  
  15.                         mqConsumerService.checkSensitiveWord4Topic(JsonUtils.toBean(data, TopicContent.class));  
  16.                         break;  
  17.                     case CommonValue.COMMENT_TAG:  
  18.                         mqConsumerService.checkSensitiveWord4Comment(JsonUtils.toBean(data, CommentContent.class));  
  19.                         break;  
  20.                     case CommonValue.TOPIC_CREATOR_BANNED_TAG:  
  21.                         mqConsumerService.updateUserTopic(JsonUtils.toBean(data, TopicCreatorBanned.class));  
  22.                         break;  
  23.                 }  
  24.             }  
  25.         } catch (Exception e) {  
  26.             logger.error("消息消费失败,{}", e);  
  27.             return Action.ReconsumeLater;  
  28.         }  
  29.         return Action.CommitMessage;  
  30.     }  
  31. }  
3.有同事提出,上面的实现方式不易维护,每多一个tag得加个case,每多一个topic得加一个if-else 这样做很蠢。

解决思路

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

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

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

[java]  view plain  copy
  1. public interface IMessageHandler<T> {  
  2.     void init();  
  3.   
  4.     String getDestination(T message);  
  5.   
  6.     JmsAction parse(T message);  
  7. }  
接着,定义消息处理器的抽象类。形如NamespaceHandlerSupport。这边要实现消息的分发和解析器的注册

[java]  view plain  copy
  1. public abstract class MessageHandlerSupport<T> implements IMessageHandler<T> {  
  2.   
  3.     private final Map<String, IMessageParser> parsers = new ConcurrentHashMap<>();  
  4.   
  5.     @Override  
  6.     public JmsAction parse(T message) {  
  7.         return findParserForMessage(message).parse(message);  
  8.     }  
  9.   
  10.     private IMessageParser findParserForMessage(T message) {  
  11.         IMessageParser parser = this.parsers.get(getDestination(message));  
  12.         if (parser == null) {  
  13.             throw new MessageParserException("No MessageParser is matched,Destination is " + getDestination(message));  
  14.         }  
  15.         return parser;  
  16.     }  
  17.   
  18.     protected final void registerMessageParser(String elementName, IMessageParser parser) {  
  19.         this.parsers.put(elementName, parser);  
  20.     }  
  21.   
  22. }  
之后,定义一个处理器容器注册类。在Spring容器启动的时候,需要进行初始化,将需要的消息处理器放入其中,顺带提供个选择处理器的方法

[java]  view plain  copy
  1. public class MessageHandlerRegister {  
  2.     private Map<String, IMessageHandler> container = new ConcurrentHashMap<>();  
  3.   
  4.     public Map<String, IMessageHandler> getContainer() {  
  5.         return container;  
  6.     }  
  7.   
  8.     public void setContainer(Map<String, IMessageHandler> container) {  
  9.         this.container = container;  
  10.     }  
  11.   
  12.     public IMessageHandler findMessageHandler(String topicName) {  
  13.         if (CollectionUtils.isEmpty(container)) {  
  14.             return null;  
  15.         } else {  
  16.             return container.get(topicName);  
  17.         }  
  18.     }  
  19. }  
最后,再对原来的Listener进行调整
[java]  view plain  copy
  1. public class AliyunMQListener implements MessageListener {  
  2.   
  3.     private static final Logger logger = LoggerFactory.getLogger(AliyunMQListener.class);  
  4.   
  5.     @Resource  
  6.     private MessageHandlerRegister messageHandlerRegister;  
  7.   
  8.     @Override  
  9.     public Action consume(Message message, ConsumeContext context) {  
  10.         IMessageHandler messageHandler = messageHandlerRegister.findMessageHandler(message.getTopic());  
  11.         if (null == messageHandler) {  
  12.             logger.warn("No MessageHandler is matched,topic is {}", message.getTopic());  
  13.         } else {  
  14.             messageHandler.parse(message);  
  15.         }  
  16.         return Action.CommitMessage;  
  17.     }  
  18.   
  19. }  
这时候,这代码就简洁多了。而且如果需要增加处理新的topic和tag,只要添加新的处理器和解析器然后进行注册。

后记

aliyun的listener中有个ConsumeContext对象。

[java]  view plain  copy
  1. package com.aliyun.openservices.ons.api;  
  2.   
  3. /** 
  4.  * 每次消费消息的上下文,供将来扩展使用 
  5.  */  
  6. public class ConsumeContext {  
  7.   
  8. }  
感觉么,consume(Message message, ConsumeContext context)这种格式和Spring里的parse(Element element, ParserContext parserContext)有点像。。

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


猜你喜欢

转载自blog.csdn.net/lppl010_/article/details/80188680