仅从@RabbitListener获取特定消息

时间:2019-06-20 05:40:35

标签: spring-boot spring-amqp spring-rabbitmq

我有将邮件发送到RabbitMQ的旧系统。 系统仅使用一个队列:q.finance.invoice,但是它有两种消息类型,其中消息类型在标头上可用。

第一种

Type : invoice.created
{
  "field_1" : "",
  "field_2" : "",
}

第二种类型

Type : invoice.paid

{
  "field_5" : "",
  "field_6" : "",
}

因此,现在我的消费者需要根据数据类型选择性地处理消息。 如果消息是在spring之前发布的,Spring就有@RabbitHandler可以这样做。 我不能使用@RabbitHandler注释。 我认为是因为@RabbitHandler正在基于旧系统中不存在的__TypeId__标头转换邮件。

我如何模拟这种@RabbitHandler行为(根据其类型获取数据)?

因此,我使用@RabbitListener来消费消息。 但是@RabbitListener正在接收所有类型的消息。 我们使用@RabbitListener的另一个原因是我们的错误处理程序取决于MessageChannel 我们拥有的基本方法签名如下:

    @RabbitListener(queues = "q.finance.invoice")
    public void listenInvoicePaid(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) {
   // convert message body JSON string to object
   // process it
}

我正在尝试根据类型进行手动拒绝,这是可行的。但是我确定当我有很多侦听器或队列时,它是不可扩展的

import java.io.IOException;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Service;

import com.rabbitmq.client.Channel;

@Service
public class InvoiceListenerOnMethod {

    private static final Logger log = LoggerFactory.getLogger(InvoiceListenerOnMethod.class);

    @RabbitListener(queues = "q.finance.invoice")
    public void listenInvoiceCreated(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag)
            throws IOException {
        if (!StringUtils.equalsIgnoreCase("invoice.created", message.getMessageProperties().getType())) {
            log.warn("[on Method] Rejecting invoice created : {}", message);
            channel.basicReject(tag, true);
            return;
        }

        log.info("[on Method] Listening invoice created : {}", message);
    }

    @RabbitListener(queues = "q.finance.invoice")
    public void listenInvoicePaid(Message message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag)
            throws IOException {
        if (!StringUtils.equalsIgnoreCase("invoice.paid", message.getMessageProperties().getType())) {
            log.warn("[on Method] Rejecting invoice paid : {}", message);
            channel.basicReject(tag, true);
            return;
        }

        log.info("[on Method] Listening invoice paid : {}", message);
    }

}

要点是,当我有4条消息(付费创建-创建)时,侦听器可以运行4次以上,因为我们无法控制谁接收哪条消息。因此,listenInvoicePaid()

可能像这样
  • reject()
  • reject()
  • ack()
  • reject()
  • ack()

与在listenInvoiceCreated()中也可以在ack()之前出现多个reject()相同的方式
因此,在正确处理所有消息之前,我总共可以打电话给大约10条消息。

有任何建议修复该代码吗?

3 个答案:

答案 0 :(得分:0)

我还没有进行过Rabbit的春季集成工作,但总的来说,只有一个队列来处理不同消息类型的想法听起来有些麻烦:

许多消费者可能会收到他们无法处理的消息类型,因此不得不拒绝它们,这样消息将回到兔子,然后一次又一次...所有集群的性能可能会恶化因此。

所以我认为您可以遵循两种路径:

  • 实现单个侦听器,该侦听器可以处理两种类型的消息。无需更改Rabbit,但在Java方面可能是具有挑战性的重构。

  • 幸运的是,Rabbit MQ在路由消息方面非常灵活。根据路由密钥,报头(无论什么类型),配置交换以将类型A的消息路由到队列A,将类型B的消息路由到队列B,Rabbit中有不同类型的Exchange,您肯定会找到最合适的配置。

我个人会选择第二条路径。

答案 1 :(得分:0)

您可以将MessagePostProcessor添加到容器工厂的afterReceiveMessagePostProcessor属性中。在后处理器中,您可以检查JSON body()并将__TypeId__标头设置为适当的类名称。

有关示例,请参见this answer

答案 2 :(得分:0)

可能的实施

这是幼稚的if-else方式,谢谢Mark。这是您的建议(第一种选择)。 至于第二种选择,我做不到,因为发布者是我没有代码的旧系统

    @RabbitListener(queues = "q.finance.invoice")
    public void listenInvoiceCreated(@Payload String message, @Header(AmqpHeaders.DELIVERY_TAG) long tag,
            @Header("type") String type) throws IOException {
        if (StringUtils.equalsIgnoreCase(type, "invoice.paid")) {
            log.info("Delegate to invoice paid handler");
        } else if (StringUtils.equalsIgnoreCase(type, "invoice.created")) {
            log.info("Delegate to invoice created handler");
        } else {
            log.info("Delegate to default handler");
        }
    }

第二种实现方式
感谢Gary,这就是我实施的方法。我认为这是更清洁的方法。接下来,我只需要将消息后处理程序提取到其他类以实现可维护性,因此就不会使我的@RabbitListener

混乱

配置文件

import java.util.Optional;

import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.boot.autoconfigure.amqp.SimpleRabbitListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.course.finance.message.invoice.InvoiceCreatedMessage;
import com.course.finance.message.invoice.InvoicePaidMessage;

@Configuration
public class RabbitmqConfig {

    @Bean(name = "rabbitListenerContainerFactory")
    public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(
            SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, connectionFactory);

        factory.setAfterReceivePostProcessors(new MessagePostProcessor() {

            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                var type = message.getMessageProperties().getHeaders().get("type").toString();
                String typeId = null;

                if (StringUtils.equalsIgnoreCase(type, "invoice.paid")) {
                    typeId = InvoicePaidMessage.class.getName();
                } else if (StringUtils.equalsIgnoreCase(type, "invoice.created")) {
                    typeId = InvoiceCreatedMessage.class.getName();
                }

                Optional.ofNullable(typeId).ifPresent(t -> message.getMessageProperties().setHeader("__TypeId__", t));

                return message;
            }

        });

        return factory;
    }

    @Bean
    Jackson2JsonMessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }

    @Bean
    RabbitTemplate rabbitTemplate(Jackson2JsonMessageConverter converter, ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        return template;
    }

}

Listener

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

import com.course.finance.message.invoice.InvoiceCreatedMessage;
import com.course.finance.message.invoice.InvoicePaidMessage;

@Service
@RabbitListener(queues = "q.finance.invoice")
public class InvoiceListener {

    private static final Logger log = LoggerFactory.getLogger(InvoiceListener.class);

    @RabbitHandler
    public void listenInvoiceCreated(InvoiceCreatedMessage message) {
        log.info("Listening invoice created : {}", message);
    }

    @RabbitHandler
    public void listenInvoicePaid(InvoicePaidMessage message) {
        log.info("Listening invoice paid : {}", message);
    }

    @RabbitHandler(isDefault = true)
    public void listenDefault(Message message) {
        log.info("Default invoice listener : {}", message.getMessageProperties().getHeaders());
    }

}