SpringBoot使用注解形式监听RocketMQ消费者进行消息消费的实现

1,599 阅读3分钟

环境准备:

RocketMQ 服务

腾讯TDMQ-RocketMQ版 实在是懒得搭RocketMQ服务, 最近发现腾讯云TDMQ在内测,不收费,所以想拿来玩玩试试,于是就有了今天的这篇文章来给大家分享, 请各位多多指教, 那么腾讯TDMQ-RocketMQ的一些配置就不多做介绍了,请自行参考官网配置方法

image.png

引入Pom依赖

接下来引入RocketMQ的依赖包:本次使用的版本为2.2.2

其实依赖中有监听注解,但用了一下,感觉不是很顺手,所以才自己写了一套

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.2</version>
</dependency>

yml配置

编写yml配置

rocketmq:
  name-server: rocketMqRemoteAddress
  consumer:
    access-key: 访问密钥
    secret-key: 加密密钥
    namespace: rocketmq命名空间

如果你同我一样使用了腾讯云的RocketMQ,name以上几个配置参数我会一一解释 rocketmq.name-server 在使用时要注意, 如果你有RocketMQ示例对应地区的服务器,请使用VPC网络接入, 如果没有请联系客服为你开通公网访问.

配置参数取自
rocketmq.name-server集群接入地址,在控制台集群管理页面的集群列表操作栏的接入地址处获取。
rocketmq.consumer.access-key角色密钥,在 角色管理 页面复制密钥列复
rocketmq.consumer.secret-key头像-> 访问管理 -> 访问密钥中复制
rocketmq.consumer.namespace命名空间的名称,在控制台命名空间页面复制,格式是**集群 ID ++ 命名空间**

定义枚举

该枚举会在后面使用

package com.***.***.enums;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;

/**
 * Mq枚举
 *
 * @author evans
 */
@Getter
@AllArgsConstructor
public enum MQEnum {
    /**
     * Mq
     */
    DEMO(1000, "描述", "topic", "group", "tag");

    private final Integer code;

    private final String describe;

    private final String topic;

    private final String groupId;

    private final String tag;
}

定义注解:

自定义注解(RocketMQConsumerListener)

以下是我个人的自定义注解, 你们可以随便哈, 此处的MqEnum即为上方的MQEnum

package com.***.***.annotation;

import com.catnet.common.enums.MQEnum;
import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/**
 * @author evans
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface RocketMQConsumerListener {
    /**
     * Mq枚举
     *{@link MQEnum}
     * @return mqEnum
     */
    MQEnum mqEnum();

}

注解扫描:

编写处理类代码

@RocketMQConsumerListener修饰的类进行注册处理,这里我们选择实现BeanPostProcessor接口进行项目启动开始注入bean时初始化MQ消费者, 感兴趣的可以自行百度BeanPostProcessor的用法

package com.***.message.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.catnet.common.annotation.RocketMQConsumerListener;
import com.catnet.common.enums.MQEnum;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.acl.common.AclClientRPCHook;
import org.apache.rocketmq.acl.common.SessionCredentials;
import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;

import java.util.Arrays;
import java.util.List;

/**
 * {@link com.catnet.common.annotation.RocketMQConsumerListener} 处理类
 *
 * @author yangbin
 */
@Slf4j
@Configuration
@AutoConfigureAfter(RocketMQProperties.class)
public class ProcessMqConsumerHandler implements BeanPostProcessor {

    @Autowired
    private RocketMQProperties mqProperties;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @SneakyThrows
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        RocketMQConsumerListener annotation = AnnotationUtils.findAnnotation(bean.getClass(), RocketMQConsumerListener.class);
        if (ObjectUtil.isNull(annotation)) {
            return bean;
        }
        // 开始创建消费者
        List<Class<?>> interfaces = Arrays.asList(bean.getClass().getInterfaces());
        if (!CollUtil.contains(interfaces, MessageListenerConcurrently.class)) {
            log.error("[{}] >>> 未实现 {} 的<onMessage>方法", bean.getClass().getName(), MessageListenerConcurrently.class.getName());
            return bean;
        }
        MQEnum mqEnum = annotation.mqEnum();
        if (ObjectUtil.isNull(mqEnum)) {
            log.error("[{}] >>> 未填写对应监听MQ枚举", bean.getClass().getName());
            return bean;
        }
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(mqProperties.getConsumer().getNamespace(),
                mqEnum.getGroupId(), new AclClientRPCHook(new SessionCredentials(mqProperties.getConsumer().getAccessKey(), mqProperties.getConsumer().getSecretKey())));
        consumer.setNamesrvAddr(mqProperties.getNameServer());
        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe(mqEnum.getTopic(), mqEnum.getTag());
        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            // 消息处理逻辑
            MessageListenerConcurrently consumerBean = (MessageListenerConcurrently) bean;
            return consumerBean.consumeMessage(msgs, context);
        });
        try {
            consumer.start();
            log.info("[MQ-Consumer:{}] >>> 已启动 -- 监听Topic:{} - GID:{} - Tag:{}", bean.getClass().getName(), mqEnum.getTopic(), mqEnum.getGroupId(), mqEnum.getTag());
        } catch (Exception ex) {
            log.error("[MQ-Consumer:{}] >>> 启动失败", bean.getClass().getName());
        }
        return bean;
    }
}

注意事项

使用@RocketMQConsumerListener 修饰的消费者需要实现 MessageListenerConcurrently 接口的 consumeMessage 方法, 然后在此方法中进行消费消息的业务逻辑处理

启动项目:

观察日志

可以看到, 我在初始化消费者时打印的日志, 标识消费者启动成功并成功监听对应Topic

image.png

查看TDMQ后台

然后登录TDMQ控制台查看Group消费者详情,可以看到已经连接了

image.png

接下来就可以使用消息生产者向对应的tag发送消息啦