MyListener作为mq消息的唯一入口,只负责分发消息到对应的处理器,具体的业务逻辑由处理器实现。后续业务扩张,增加处理器即可,无需修改原来的代码。
使用RocketMqNotice注解 标记 消息处理器类,传入topic 和 tag
package com.example.demo.rocketmq;
import java.lang.annotation.*;
/**
* 自定义注解,标识 rocketmq的消息的处理器,处理指定的 topic tag
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RocketMqNotice {
String topic() default "";
String tag() default "";
}
(1)Listener初始化时,获取有自定义注解RocketMqNotice的bean(标识消息处理器)。
并且根据topic和tag归类。
(2)Listener消费消息时,根据topic和tag获取处理器。
只负责把消息转发到对应的处理器,具体的业务逻辑由处理器实现。
达到解耦的目的,支持一个消息转发到多个处理器。
package com.example.demo.rocketmq.consumer;
import com.example.demo.rocketmq.RocketMqNotice;
import com.example.demo.rocketmq.HandlerInterface;
import org.apache.commons.lang3.StringUtils;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class MyListener implements ApplicationContextAware, MessageListenerOrderly {
private ApplicationContext applicationContext;
private final Map<String, List<Object>> handlerMap = new ConcurrentHashMap<>();
@Override
public ConsumeOrderlyStatus consumeMessage(List<MessageExt> list, ConsumeOrderlyContext consumeOrderlyContext) {
consumeOrderlyContext.setAutoCommit(true);
for (MessageExt msg : list) {
handleNotification(msg);
}
return ConsumeOrderlyStatus.SUCCESS;
}
/**
* =================核心代码=====================
* 根据消息的 topic - tag
* 从map中获取 处理器列表
* 转发消息到 每个处理器
*/
private void handleNotification(Message message) {
String topic = message.getTopic();
String tag = message.getTags();
String key = topic + "-" + tag;
if (CollectionUtils.isEmpty(handlerMap)) {
initHandleMap();
}
List<Object> handlers = handlerMap.get(key);
if (!CollectionUtils.isEmpty(handlers)) {
for (Object handler : handlers) {
((HandlerInterface) handler).onNotification(message);
}
}
}
/**
* 初始化消息处理器map
*
* 1.获取所有标记了自定义注解RocketMqNotice的处理器对象
* 2.判断是否是 处理器接口HandlerInterface的实现类
* 3.再次获取实现类的注解RocketMqNotice,校验
* 4.过滤掉注解的topic,tag为空的处理器对象
* 5.根据注解的 Topic-Tag 对处理器归类
*/
private void initHandleMap() {
Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(RocketMqNotice.class);
if (!CollectionUtils.isEmpty(handlers)) {
for (Object handler : handlers.values()) {
if (handler instanceof HandlerInterface) {
RocketMqNotice consumerAnno = handler.getClass().getAnnotation(RocketMqNotice.class);
if (consumerAnno == null) {
continue;
}
String annoTopic = consumerAnno.topic();
String annoTag = consumerAnno.tag();
if (StringUtils.isEmpty(annoTopic) || StringUtils.isEmpty(annoTag)) {
continue;
}
String key = annoTopic + "-" + annoTag;
List<Object> handlerList = handlerMap.getOrDefault(key, new ArrayList<>());
handlerList.add(handler);
handlerMap.put(key, handlerList);
}
}
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
package com.example.demo.rocketmq;
import org.apache.rocketmq.common.message.Message;
/**
* MQ消息处理策略 接口类
*/
public interface HandlerInterface {
/**
* 消息处理方法
*/
void onNotification(Message message);
}
消息处理器 抽象类实现 消息处理策略接口。
package com.example.demo.rocketmq;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.message.Message;
/**
* mq消息处理器 抽象类
*/
@Slf4j
public abstract class AbstractHandler implements HandlerInterface {
@Override
public void onNotification(Message message) {
try {
handlerMessage(message);
} catch (Exception e) {
log.error("推送失败,", e);
}
}
public void handlerMessage(Message message) {
}
}
继承处理器抽象类
package com.example.demo.rocketmq.handler;
import com.example.demo.rocketmq.AbstractHandler;
import com.example.demo.rocketmq.RocketMqNotice;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.message.Message;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
@Component
@Slf4j
@RocketMqNotice(topic = "TOPIC1", tag = "TAG1")
public class MyHandler1 extends AbstractHandler {
@Override
public void handlerMessage(Message message) {
log.info("处理器1:接收的消息为topic:{},tag:{},content:{}",
message.getTopic(), message.getTags(), new String(message.getBody(), StandardCharsets.UTF_8));
}
}
package com.example.demo.rocketmq.handler;
import com.example.demo.rocketmq.AbstractHandler;
import com.example.demo.rocketmq.RocketMqNotice;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.common.message.Message;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
@Component
@Slf4j
@RocketMqNotice(topic = "TOPIC2", tag = "TAG2")
public class MyHandler2 extends AbstractHandler {
@Override
public void handlerMessage(Message message) {
log.info("处理器2:接收的消息为topic:{},tag:{},content:{}",
message.getTopic(), message.getTags(), new String(message.getBody(), StandardCharsets.UTF_8));
}
}
package com.example.demo.rocketmq.test;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@ConfigurationProperties(prefix = "rocketmq.producer")
@Data
public class MQProducerProperties {
private String namesrvAddr = "127.0.0.1:9876";
private String producerGroup = "producerGroup";
}
package com.example.demo.rocketmq.consumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.remoting.protocol.heartbeat.MessageModel;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Map;
@Slf4j
@Component
public class MyConsumer {
@Resource
private MQConsumerProperties properties;
@Resource
private MyListener myListener;
/**
* 初始化消费者信息
*/
@PostConstruct
public void init() throws MQClientException {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(properties.getConsumerGroup());
consumer.setNamesrvAddr(properties.getNamesrvAddr());
consumer.setInstanceName(String.valueOf(System.currentTimeMillis()));
Map<String, String> topicAndTag = properties.getTopicAndTag();
if (CollectionUtils.isEmpty(topicAndTag)) {
consumer.subscribe(properties.getDefaultTopic(), "*");
} else {
for (Map.Entry<String, String> entry : topicAndTag.entrySet()) {
String topic = entry.getKey();
String tag = entry.getValue();
consumer.subscribe(topic, tag);
}
}
consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET);
String msgMode = properties.getMsgModel();
MessageModel messageModel = "broadcast".equalsIgnoreCase(msgMode) ? MessageModel.BROADCASTING : MessageModel.CLUSTERING;
consumer.setMessageModel(messageModel);
consumer.registerMessageListener(myListener);
consumer.start();
log.info("Consumer OK to start with topicAndTag[" + topicAndTag.toString() + "]");
}
}
package com.example.demo.rocketmq.test;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Map;
@Component
@ConfigurationProperties(prefix = "rocketmq.producer")
@Data
public class MQProducerProperties {
private String namesrvAddr = "127.0.0.1:9876";
private String producerGroup = "producerGroup";
}
package com.example.demo.rocketmq.test;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.common.message.Message;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
@Slf4j
@Component
public class MyProducer {
@Resource
MQProducerProperties properties;
@Getter
private DefaultMQProducer producer;
/**
* Spring bean init-method
*/
@PostConstruct
public void init() throws MQClientException {
producer = new DefaultMQProducer(properties.getProducerGroup());
producer.setNamesrvAddr(properties.getNamesrvAddr());
producer.setInstanceName(String.valueOf(System.currentTimeMillis()));
producer.setDefaultTopicQueueNums(1);
producer.start();
log.info("Producer OK to start");
}
/**
* Spring bean destroy-method
*/
public void destroy() {
producer.shutdown();
}
/**
* 发送消息
*/
public boolean sendMessage(Message msg) {
SendResult sendResult = null;
try {
sendResult = producer.send(msg);
} catch (Exception e) {
e.printStackTrace();
return false;
}
if (sendResult == null || sendResult.getSendStatus() != SendStatus.SEND_OK) {
log.error("Failed to send message,SendStatus is not OK");
return false;
}
log.info("MsgId:" + sendResult.getMsgId());
return true;
}
}
rocketmq:
producer:
namesrvAddr: 127.0.0.1:9876
producerGroup: producerGroup-1712
consumer:
namesrvAddr: 127.0.0.1:9876
consumerGroup: consumerGroup-1712
msgModel: CLUSTERING
topicAndTag:
TOPIC1: TAG1
TOPIC2: TAG2
TOPIC3: TAG3