rocketmq Listener 还可以这样配置(基于SPEL\限定符号\自动连线)

文章介绍了如何在SpringBoot应用中配置ApacheRocketMQ的消费者,包括创建Consumer实例,设置NameServer,订阅Topic,并使用MessageListener和MessageHandler处理接收到的消息,同时涉及自定义过滤和SpEL表达式的应用。
摘要由CSDN通过智能技术生成

配置Consumers

package .config;

import .message.listener.MyMessageListener0;
import .message.listener.MyMessageListener1;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.exception.MQClientException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author Jay
 */
@Configuration
public class RocketMQConsumerConfig {

    @Bean(name = "consumer0", initMethod = "start", destroyMethod = "shutdown")
    public DefaultMQPushConsumer consumer0(MessageListenerConcurrently myMessageListener0) throws MQClientException {
        // 初始化consumer,并设置 consumer group name
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("DEFAULT_CONSUMER_GROUP0");

        // 设置NameServer地址
        consumer.setNamesrvAddr("localhost:9876");
        //订阅一个或多个topic,并指定tag过滤条件,这里指定*表示接收所有tag的消息
        consumer.subscribe("TEST_TOPIC0", "*");

        //注册回调接口来处理从Broker中收到的消息
        consumer.registerMessageListener(myMessageListener0);
        return consumer;
    }

    @Bean(name = "consumer1", initMethod = "start", destroyMethod = "shutdown")
    public DefaultMQPushConsumer consumer1(MessageListenerConcurrently myMessageListener1) throws MQClientException {
        // 初始化consumer,并设置 consumer group name
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("DEFAULT_CONSUMER_GROUP1");

        // 设置NameServer地址
        consumer.setNamesrvAddr("localhost:9876");
        //订阅一个或多个topic,并指定tag过滤条件,这里指定*表示接收所有tag的消息
        consumer.subscribe("TEST_TOPIC1", "*");

        //注册回调接口来处理从Broker中收到的消息
        consumer.registerMessageListener(myMessageListener1);
        return consumer;
    }
}

声明注解 MessageHandlerAnn 

package message.ann;

import org.springframework.stereotype.Component;

import java.lang.annotation.*;

/**
 * @author Jay
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MessageHandlerAnn {

    /**
     * 通过SpEL 表达式计算 #messageExt 的相关信息是否满足所有条件
     *
     * @return
     */
    String []eLFilter() default "";

}

 消息消费类MessageHandler

package message.handler;

import org.apache.rocketmq.common.message.MessageExt;

/**
 * @author Jay
 * @param <T> 消息 body 的数据类型,如果没有重写 convertMessage 方法, 则建议<T> 为 String
 *            消费 RocketMQ 消息的帮助类
 */
public interface MessageHandler<T> extends InitializingBean {

    default void afterPropertiesSet() {
        if (this.getClass().getAnnotation(Qualifier.class) == null ||
                this.getClass().getAnnotation(Component.class) == null) {
            throw new RuntimeException("MessageHandler 实现类必须被 @Qualifier 和 @Component 注解修饰");
        }
    }

    /**
     *  是否要保留此消息
     * @param messageExt messageExt
     * @return TRUE 保留 FALSE 跳过
     */
    default Boolean keepMessage(MessageExt messageExt){ return Boolean.TRUE;}

    /**
     * @param messageExt 消息
     * @return 消息体已经转换过的信息
     */
    default T getMessageBodyThenConvertType(MessageExt messageExt) {
        return (T) new String(messageExt.getBody());
    }

    /**
     * 具体处理消息的逻辑
     *
     * @param message messageExt.body
     */
    void consumerMessage(T message);

}

消费实现类三个

package message.handler;

import message.ann.MessageHandlerAnn;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;


/**
 *
 * @author Jay
 */
@Component
@Qualifier("TEST_TOPIC0")
@MessageHandlerAnn(eLFilter = {"#messageExt.topic == 'TEST_TOPIC0'", "#messageExt.tags == 'tag0'"})
public class Topic0Tag0MessageHandler implements MessageHandler<String> {

    @Override
    public void consumerMessage(String message) {
        System.out.println("This is Topic0Tag0MessageHandler"+message);
    }

}


package message.handler;

import message.ann.MessageHandlerAnn;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * @author Jay
 */ 
@Component
@Qualifier("TEST_TOPIC0")
@MessageHandlerAnn(eLFilter = {"#messageExt.topic == 'TEST_TOPIC0'", "#messageExt.tags == 'tag1'"})
public class Topic0Tag1MessageHandler implements MessageHandler<String> {

    @Override
    public void consumerMessage(String message) {
        System.out.println("This is Topic0Tag1MessageHandler"+message);
    }

}

package message.handler;

import message.ann.MessageHandlerAnn;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;


/**
 * @author Jay
 */

@Component
@Qualifier("TEST_TOPIC1")
@MessageHandlerAnn(eLFilter = {"#messageExt.topic == 'TEST_TOPIC1'", "#messageExt.tags == 'tag0'"})
public class Topic1Tag0MessageHandler implements MessageHandler<String> {

    @Override
    public void consumerMessage(String message) {
        System.out.println("This is Topic1Tag0MessageHandler" + message);
    }

}

消息监听者MessageListener

package message.listener;

import message.ann.MessageHandlerAnn;
import message.handler.MessageHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Optional;

/**
 * @author Jay
 */
@Slf4j
public class MessageListener implements MessageListenerConcurrently,InitializingBean {

    final private List<MessageHandler<?>> messageHandlerList;

    public MyMessageListener(List<MessageHandler<?>> messageHandlerList) {
        this.messageHandlerList = messageHandlerList;
    }

    @Override
    public void afterPropertiesSet() {
        // messageHandlerList 不能为空
        if (ObjectUtils.isEmpty(this.messageHandlerList)) {
            log.error("messageHandlerList is empty");
            throw new RuntimeException("messageHandlerList is empty");
        }
    }

    @SuppressWarnings("all")
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
        for (MessageExt messageExt : msgs) {
            log.debug("received msg: {}", messageExt);
            try {
                long now = System.currentTimeMillis();
                // 预设 EL 上下文
                EvaluationContext cont = SimpleEvaluationContext.forReadWriteDataBinding()
                        .build();
                cont.setVariable("messageExt", messageExt);

                // 获取合适的 MessageHandler
                Optional<MessageHandler<?>> messageHandlerOpt = filterListThenGetFirstHandler(this.messageHandlerList, cont, messageExt);
                if (!messageHandlerOpt.isPresent()) {
                    // 需要检查 filterListThenGetFirstHandler 中两处 filter 处理后为什么会返回空.
                    log.error("没有合适的MessageHandler");
                }
                MessageHandler messageHandler = messageHandlerOpt.get();

                // 消息体转换并消费消息
                consumer(messageExt, messageHandler);

                long costTime = System.currentTimeMillis() - now;
                log.debug("consume {} cost: {} ms", messageExt.getMsgId(), costTime);
            } catch (Exception e) {
                log.warn("consume message failed. messageExt:{}", messageExt, e);
                return ConsumeConcurrentlyStatus.RECONSUME_LATER;
            }
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }

    protected Optional<MessageHandler<?>> filterListThenGetFirstHandler(List<MessageHandler<?>> messageHandlerList,
                                                                        final EvaluationContext cont, MessageExt messageExt) {
        if (CollectionUtils.isEmpty(messageHandlerList)) {
            log.error("messageHandlerList is empty");
            return Optional.empty();
        }
        return messageHandlerList.stream()
                /* 处理 MessageHandler关于自定义的过滤函数 */
                .filter(messageHandler -> messageHandler.keepMessage(messageExt))
                /* 处理 MessageHandlerAnn 的表达式 */
                .filter(messageHandler -> {
                    MessageHandlerAnn messageHandlerAnn = messageHandler.getClass()
                            .getAnnotation(MessageHandlerAnn.class);
                    if (messageHandlerAnn == null) {
                        return Boolean.TRUE;
                    }
                    String[] elFilterArray = messageHandlerAnn
                            .eLFilter();
                    if (elFilterArray == null) {
                        return Boolean.TRUE;
                    }
                    for (String elFilter : elFilterArray) {
                        Boolean aBoolean = new SpelExpressionParser()
                                .parseExpression(elFilter)
                                .getValue(cont, Boolean.class);
                        if (Boolean.FALSE.equals(aBoolean)) {
                            return Boolean.FALSE;
                        }
                    }
                    // 通过SpEL 表达式计算 #messageExt 的相关信息是否满足所有条件
                    return Boolean.TRUE;
                })
                .findFirst();
    }
    
    protected void consumer(MessageExt messageExt, MessageHandler messageHandler) {
        messageHandler.consumerMessage(messageHandler.getMessageBodyThenConvertType(messageExt));
    }

}

工厂方法创建MyMessageListener 实例

    // 放置在 RocketMQConsumerConfig 类中
    @Bean("myMessageListener0")
    public MessageListenerConcurrently myMessageListener0(@Qualifier("TEST_TOPIC0") List<MessageHandler<?>> messageHandlerList) {
        return new MyMessageListener(messageHandlerList);
    }

    @Bean("myMessageListener1")
    public MessageListenerConcurrently myMessageListener1(@Qualifier("TEST_TOPIC1") List<MessageHandler<?>> messageHandlerList) {
        return new MyMessageListener(messageHandlerList);
    }

 实现记录

思路来源

Fine-tuning Annotation-based Autowiring with Qualifiers :: Spring Framework

Using @Autowired :: Spring Framework

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以使用XML配置来设置KafkaListener。以下是一个示例配置: 首先,在您的Spring应用程序的XML配置文件中,添加以下命名空间声明: ```xml <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> ``` 然后,配置KafkaListener容器工厂和KafkaTemplate: ```xml <bean id="kafkaListenerContainerFactory" class="org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory"> <property name="consumerFactory" ref="consumerFactory"/> <!-- 其他配置属性 --> </bean> <bean id="consumerFactory" class="org.springframework.kafka.core.DefaultKafkaConsumerFactory"> <constructor-arg> <map> <entry key="bootstrap.servers" value="localhost:9092"/> <!-- 其他配置属性 --> </map> </constructor-arg> </bean> <bean id="kafkaTemplate" class="org.springframework.kafka.core.KafkaTemplate"> <constructor-arg> <bean class="org.apache.kafka.clients.producer.ProducerConfig"> <constructor-arg> <props> <prop key="bootstrap.servers">localhost:9092</prop> <!-- 其他配置属性 --> </props> </constructor-arg> </bean> </constructor-arg> </bean> ``` 接下来,定义一个带有@KafkaListener注解的消息监听器方法: ```java public class MessageListener { @KafkaListener(topics = "myTopic") public void listen(ConsumerRecord<String, String> record) { // 处理接收到的消息 String message = record.value(); System.out.println("Received message: " + message); } } ``` 最后,在XML配置文件中将该监听器注册为Spring bean: ```xml <bean id="messageListener" class="com.example.MessageListener"/> ``` 这样,您就可以通过XML配置设置KafkaListener,并处理接收到的消息了。请根据您的实际需求进行相应的配置和调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值