(15)RabbitMQ与SpringBoot的整合使用

本节将会介绍一下RabbitMQ与SpringBoot的整合使用,而且会实现设置confirm和return回调确认,并且会对 "Only one ConfirmCallback is supported by each RabbitTemplate"和"Only one ReturnCallback is supported by each RabbitTemplate"出现的原因做出说明并给出相应的解决办法。

概述

上一节 《(14)RabbitMQ与Spring的整合使用》中已经说过xml中的那些<rabbit:xxx>标签对应的其实就是Spring提供的一些类,比如:CachingConnectionFactory、RabbitAdmin、RabbitTemplate、MessageListenerContainer。。。。。。RabbitMQ与SpringBoot整合用的是spring-boot-starter-amqp,我们只需要在配置文件中配置一下,通过自动装配就可以自动创建我们需要的Bean。

整合操作

工程结构

项目GitHub地址 https://github.com/RookieMember/RabbitMQ-Learning.git。项目采用maven构建,rabbitmq-springboot-demo是parent工程,下面有三个module:rabbitmq-springboot-common工程里面就定义了一个Order类;rabbitmq-springboot-producer和rabbitmq-springboot-consumer分别是消息生产者和消费者,二者都依赖rabbitmq-springboot-common工程。

rabbitmq-springboot-demo项目

parent工程rabbitmq-springboot-demo依赖如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.wkp</groupId>
    <artifactId>rabbitmq-springboot-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rabbitmq-springboot-demo</name>
    <packaging>pom</packaging>

    <modules>
        <module>rabbitmq-springboot-common</module>
        <module>rabbitmq-springboot-producer</module>
        <module>rabbitmq-springboot-consumer</module>
    </modules>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.mockito</groupId>
                    <artifactId>mockito-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

common工程

项目的pom.xml就不贴了,定义的Order类如下:注意要实现Serializable接口,以便进行序列化

package com.wkp.common.model;

import java.io.Serializable;

public class Order implements Serializable {
    private static final long serialVersionUID = -3156591354312323717L;

    private long orderId;
    private String orderNo;
    private String orderDesc;
    public Order(long orderId, String orderNo, String orderDesc) {
        this.orderId = orderId;
        this.orderNo = orderNo;
        this.orderDesc = orderDesc;
    }

    public long getOrderId() {
        return orderId;
    }
    public void setOrderId(long orderId) {
        this.orderId = orderId;
    }
    public String getOrderNo() {
        return orderNo;
    }
    public void setOrderNo(String orderNo) {
        this.orderNo = orderNo;
    }
    public String getOrderDesc() {
        return orderDesc;
    }
    public void setOrderDesc(String orderDesc) {
        this.orderDesc = orderDesc;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderId=" + orderId +
                ", orderNo='" + orderNo + '\'' +
                ", orderDesc='" + orderDesc + '\'' +
                '}';
    }
}

消费者工程

消费者项目如下,ConsumerApplication为SpringBoot启动类,ConsumerRabbitConfig为消费者配置处理类

application.properties内容如下:主要定义了连接信息,消费者签收模式,并行消费者数量

#连接信息
spring.rabbitmq.addresses=127.0.0.1
spring.rabbitmq.username=wkp
spring.rabbitmq.password=123456
spring.rabbitmq.virtual-host=/myvhost
spring.rabbitmq.connection-timeout=15000ms

#设置签收模式为手动签收,并行消费数量,最大并行消费数量
spring.rabbitmq.listener.simple.acknowledge-mode=manual
spring.rabbitmq.listener.simple.concurrency=2
spring.rabbitmq.listener.simple.max-concurrency=5

应用启动类很简单

package com.wkp.springboot.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ConsumerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }

}

 下面看下消费者的配置处理类:

程序声明交换机、队列、绑定的方式主要有两种:

  • 通过@Bean注解的方式;
  • 通过 @RabbitListener(bindings = @QueueBinding( ...... )),主要用到了@QueueBinding、@Queue、@Exchange这几个注解,用于声明绑定、队列和交换机。

消费消息主要有两种方式:

1、通过消息监听容器MessageListenerContainer:下面代码中被我注释掉了的SimpleMessageListenerContainer就是一种实现,我们通过消息监听容器可以设置消费者签收模式、消费者标签策略、消息转换器、并行消费数量、预抓取数量、异常处理。。。。。。当然,前面的这些设置我们也可以通过在application中配置,由SpringBoot的自动装配功能帮我们自动创建。还有一点,就是这个容器可以同时监听多个队列,对多个队列进行统一设置处理。

2、另一种就是通过注解@RabbitListener和@RabbitHandler,

@RabbitListener可以加在类或者方法上,用于标注当前类或方法是一个消息监听器,一般结合@RabbitHandler进行使用。@RabbitListener(queues = "") 也可以指定多个队列进行消费。

@RabbitListener(containerFactory="")注解中还有一个关键的属性containerFactory,containerFactory是消息监听容器工厂,如果不指定会使用默认的(这个根据配置spring.rabbitmq.listener.simple/direct决定),其生产MessageListenerContainer,设置的属性也差不多(消费者签收模式、消费者标签策略、消息转换器、并行消费数量、预抓取数量、异常处理。。。。。。)

使用这两个注解的消息处理方法是通过MessageConverter转化的,可以通过RabbitListenerContainerFactory 去设置我们自定义的消息转换器,下面代码中有个被我注释掉的方法中就定义了一个SimpleRabbitListenerContainerFactory,然后可以通过@RabbitListener(containerFactory="自定义消息监听容器bean名称")的方式指定使用我们自己的containerFactory。

消息的 content_type 属性表示消息 body 数据以什么数据格式存储,接收消息除了使用 Message 对象接收消息(包含消息属性等信息)之外,还可直接使用对应类型接收消息 body 内容,但若方法参数类型不正确会抛异常:

  • application/octet-stream:二进制字节数组存储,使用 byte[]
  • application/x-java-serialized-object:java 对象序列化格式存储,使用 Object、相应类型(反序列化时类型应该同包同名,否则会抛出找不到类异常)
  • text/plain:文本数据类型存储,使用 String
  • application/json:JSON 格式,使用 Object、相应类型

下面的消费者代码示例中,我写了两个消费方法comsumeA(@Payload Order order,Channel channel,@Headers Map<String, Object> headers)、comsumeB(Message message, Channel channel),分别通过Message、@Payload注解结合Order类接收的消息内容,另外我们还可以根据消息的内容将消费者方法定义为类型这样的:comsumeC(String msg)、comsumeD(Object msg)、comsumeE(byte[] msg)。。。。。。方法中的channel,header等参数都是可选的,可以根据自己的需求进行添加。如果英语好一点,可以看下@RabbitListener的说明

下面是消费者代码 

package com.wkp.springboot.consumer;

import com.rabbitmq.client.Channel;
import com.wkp.common.model.Order;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.amqp.support.ConsumerTagStrategy;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.util.ErrorHandler;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Map;

@Configuration
public class ConsumerRabbitConfig {

    Logger logger = LoggerFactory.getLogger(ConsumerRabbitConfig.class);
    @Resource
    private ConnectionFactory connectionFactory;

    @Bean
    public TopicExchange exchange() {
        return new TopicExchange("springboot_direct_exchange", true, false);
    }

    @Bean
    public Queue queueA() {
        return new Queue("direct_queue", true);
    }

    @Bean
    public Binding bindingA() {
        return BindingBuilder.bind(queueA()).to(exchange()).with("direct_Key");
    }

    //上面@Bean注解和下面的@RabbitListener都是可以自动声明绑定,交换机和队列的
    /**
     * 注解式消息消费及(交换机,队列)声明绑定
     *
     * @param message
     * @param channel
     * @throws IOException
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @org.springframework.amqp.rabbit.annotation.Queue(value = "topic_queue",
                    durable = "true"),
            exchange = @org.springframework.amqp.rabbit.annotation.Exchange(value = "springboot_topic_exchange",
                    durable = "true",
                    type = "topic",
                    ignoreDeclarationExceptions = "true"),
            key = "springboot.*"
    )
    )
    @RabbitHandler
    public void comsumeB(Message message, Channel channel) throws IOException {
        logger.info("RabbitListener-消费者B收到消息:{}", message.getPayload());
        Long deliveryTag = (Long)message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
        /**
         * 消费如果抛出了异常,处理方式:
         * 如果是手动签收模式,可以try catch包括,catch到了异常进行重回队列或者进行落库等操作
         * 如果是自动签收,默认会重回队列,然后一直循环重复消费。可以设置消息重新投递(设置最大投递次数,投递时间间隔,达到最大投递次数后是否重回队列等)
         */
        //模拟消费时抛出异常
//        int i=1/0;
        //手工ACK
        channel.basicAck(deliveryTag, false);
    }

    @RabbitListener(queues = "direct_queue")
    @RabbitHandler
    public void comsumeA(@Payload Order order,
                         Channel channel,
                         @Headers Map<String, Object> headers) throws IOException {
        logger.info("RabbitListener-消费者A收到消息:{}", order);
        Long deliveryTag = (Long)headers.get(AmqpHeaders.DELIVERY_TAG);
        channel.basicAck(deliveryTag, false);
    }

    //通过SimpleMessageListenerContainer消息监听容器消费消息,可以同时监听多个队列,可以设置消费者标签策略,预抓取数量,并行消费数量,消息转换器等
//    @Bean
//    public SimpleMessageListenerContainer simpleMessageListenerContainer() {
//        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
//        container.setConcurrentConsumers(2);//并行消费者数量
//        container.setMaxConcurrentConsumers(5);//最大消费者数量
//        container.setPrefetchCount(10);
//        container.setQueueNames("direct_queue", "topic_queue");
//        container.setAcknowledgeMode(AcknowledgeMode.MANUAL);//手动签收
//        //消费者标签策略
//        container.setConsumerTagStrategy(new ConsumerTagStrategy() {
//            @Override
//            public String createConsumerTag(String queue) {
//                return queue + "_tag";
//            }
//        });
        container.setMessageConverter(new Jackson2JsonMessageConverter());//消息转换器
//        container.setErrorHandler(new ErrorHandler() {
//            @Override
//            public void handleError(Throwable t) {
//
//            }
//        });
//        container.setChannelAwareMessageListener(new ChannelAwareMessageListener() {
//            @Override
//            public void onMessage(org.springframework.amqp.core.Message message, Channel channel) throws Exception {
//                MessageProperties props = message.getMessageProperties();
//                logger.info("MessageListenerContainer-消费者收到消息:{},consumerQueue:{},consumerTag:{}", new String(message.getBody()),props.getConsumerQueue(),props.getConsumerTag());
//                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
//            }
//        });
//        return container;
//    }

    //我们可以自定义RabbitListenerContainerFactory并设置消息转换器等,如果不设置,系统会自动帮我们创建一个SimpleRabbitListenerContainerFactory
//    @Bean
//    public RabbitListenerContainerFactory rabbitListenerContainerFactory(){
//        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
//        factory.setConnectionFactory(connectionFactory);
//        factory.setConcurrentConsumers(2);//并行消费者数量
//        factory.setMaxConcurrentConsumers(5);//最大消费者数量
//        factory.setPrefetchCount(10);
//        factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);//手动签收
//        //消费者标签策略
//        factory.setConsumerTagStrategy(new ConsumerTagStrategy() {
//            @Override
//            public String createConsumerTag(String queue) {
//                return queue + "_tag";
//            }
//        });
//        factory.setMessageConverter(new Jackson2JsonMessageConverter());//消息转换器
//        factory.setErrorHandler(new ErrorHandler() {
//            @Override
//            public void handleError(Throwable t) {
//
//            }
//        });
//        return factory;
//    }
}

 生产者工程

生产者工程结构如下:其中ConfirmCallBackHandler和ReturnCallBackHandler是用来处理生产者的confirm和return回调的,为了区分命名分别用了A、B结尾,其实里面的内容除了打印的日志有点区别,其他都一样;ProducerApplication是启动类,跟消费者差不多就不贴了;ProducerRabbitConfig是生产者的配置相关;RabbitSender是发送消息的封装。

先看下application.properties配置:开启了confirm和return,关于重试相关的配置是可选的

#连接信息
spring.rabbitmq.addresses=127.0.0.1
spring.rabbitmq.username=wkp
spring.rabbitmq.password=123456
spring.rabbitmq.virtual-host=/myvhost
spring.rabbitmq.connection-timeout=15000ms

#开启confirm模式
spring.rabbitmq.publisher-confirms=true
#开启return模式
spring.rabbitmq.publisher-returns=true
spring.rabbitmq.template.mandatory=true

#启用重试策略
spring.rabbitmq.template.retry.enabled=true
#最大尝试次数3
spring.rabbitmq.template.retry.max-attempts=3
#初始两次尝试时间间隔2000ms
spring.rabbitmq.template.retry.initial-interval=2000ms
#两次尝试时间间隔最大不能超过5000ms
spring.rabbitmq.template.retry.max-interval=5000ms
#下次尝试时间间隔是上次时间间隔的倍数(尝试时间间隔为2000ms,2000ms*2,2000ms*2*2......)
spring.rabbitmq.template.retry.multiplier=2

confirm回调类,只贴了一个,另一个就改了log输出

package com.wkp.springboot.producer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.stereotype.Service;

/**
 * 做消息可靠性投递
 */
@Service
public class ConfirmCallBackHandlerA implements RabbitTemplate.ConfirmCallback {

    Logger logger = LoggerFactory.getLogger(ConfirmCallBackHandlerA.class);

    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        //根据回调的correlationDataId判断发送的哪条消息投递成功,哪条没有成功
        String correlationDataId =null;
        if(correlationData!=null){
            correlationDataId = correlationData.getId();
        }
        if(ack){
            logger.info("A-CONFIRM机制:消息投递成功:correlationId:{}",correlationDataId);
        }else{
            //TODO 消息投递失败,重新投递
            logger.warn("A-CONFIRM机制:消息投递失败:correlationId:{},cause:{}",correlationDataId,cause);
        }
    }
}

return回调类,只贴了一个,另一个就改了log输出

package com.wkp.springboot.producer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;

/**
 * 消息路由失败
 */
@Service
public class ReturnCallBackHandlerA implements RabbitTemplate.ReturnCallback {

    Logger logger = LoggerFactory.getLogger(ConfirmCallBackHandlerA.class);

    @Override
    public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        String msg = new String(message.getBody());
        logger.warn("A-RETURN机制:消息路由失败,msg:{},replyCode:{},replyText:{},exchange:{},routingKey:{}",msg,replyCode,replyText,exchange,routingKey);
    }

}

下面看下ProducerRabbitConfig类:

package com.wkp.springboot.producer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import javax.annotation.Resource;

@Configuration
public class ProducerRabbitConfig {

    Logger logger = LoggerFactory.getLogger(ProducerRabbitConfig.class);

    @Resource
    private ConnectionFactory connectionFactory;
    @Resource
    private ConfirmCallBackHandlerA confirmCallBackHandlerA;
    @Resource
    private ReturnCallBackHandlerA returnCallBackHandlerA;

    /**
     * 单例的RabbitTemplate<br/>
     * 并且设置了confirm和return回调
     * @return
     */
    @Bean
    public RabbitTemplate singleRabbitTemplate(){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setConfirmCallback(confirmCallBackHandlerA);
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setReturnCallback(returnCallBackHandlerA);
        return rabbitTemplate;
    }

    /**
     * 多例的RabbitTemplate
     * @return
     */
    @Bean
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public RabbitTemplate scopeRabbitTemplate(){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        rabbitTemplate.setMandatory(true);
        return rabbitTemplate;
    }
}

可以看到定义了两个RabbitTemplate,一个是单例的,另一个是多例的。可能你看到这有点迷惑,为什么要定义两个Bean,而且为什么还有一个是多例的呢?

在回答这个问题之前,我们先说一下RabbitTemplate设置confirm和return回调的方法源码如下:我们看到setConfirmCallback() 和setReturnCallback()都只能设置一次,否则就会报异常:Only one ConfirmCallback is supported by each RabbitTemplate,Only one ReturnCallback is supported by each RabbitTemplate

而解决这个问题的办法有两种:

  • 将RabbitTemplate设置为单例,并设置两个回调方法,这样所有采用此RabbitTemplate发送消息的都会应用这两个回调,有些我们不想设置confirm的地方也会触发该回调函数,使用方面不够灵活
  • 将RabbitTemplate设置为多例,每次使用RabbitTemplate都通过applicationContext.getBean()的方式获取,这样每次获取到的都是一个新的RabbitTemplate实例,发送消息时根据业务需要自己添加相应的回调方法,相对来说更加灵活一点

回到上面的问题,我定义两个RabbitTemplate的目的是为了演示上面的两种解决办法。

public void setConfirmCallback(ConfirmCallback confirmCallback) {
	Assert.state(this.confirmCallback == null || this.confirmCallback == confirmCallback,
			"Only one ConfirmCallback is supported by each RabbitTemplate");
	this.confirmCallback = confirmCallback;
}

public void setReturnCallback(ReturnCallback returnCallback) {
	Assert.state(this.returnCallback == null || this.returnCallback == returnCallback,
			"Only one ReturnCallback is supported by each RabbitTemplate");
	this.returnCallback = returnCallback;
}

然后我们在看一下RabbitSender类:里面两个发送的方法,分别使用了单例和多例的RabbitTemplate,并且多例的RabbitTemplate也设置了另外的两个confirm和return的回调实现

package com.wkp.springboot.producer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Map;
import java.util.UUID;

@Service
public class RabbitSender {
    Logger logger = LoggerFactory.getLogger(RabbitSender.class);

    @Resource
    private RabbitTemplate singleRabbitTemplate;
    @Resource
    private ConfirmCallBackHandlerB confirmCallBackHandlerB;
    @Resource
    private ReturnCallBackHandlerB returnCallBackHandlerB;
    @Resource
    private ApplicationContext applicationContext;

    /**
     * 每次获取新的RabbitTemplate
     * @return
     */
    private RabbitTemplate getScopeRabbitTemplate(){
        RabbitTemplate scopeRabbitTemplate = (RabbitTemplate) applicationContext.getBean("scopeRabbitTemplate");
        logger.info("获取新的RabbitTemplate:{}",scopeRabbitTemplate );
        return scopeRabbitTemplate;
    }

    /**
     * 采用多例的RabbitTemplate发送消息方法调用: 构建Message消息<br/>
     * 根据自己的需要添加confirm和return回调
     * @param message
     * @param properties
     * @throws Exception
     */
    public void scopeSend(Object message, Map<String, Object> properties) throws Exception {
        RabbitTemplate rabbitTemplate=getScopeRabbitTemplate();
        rabbitTemplate.setConfirmCallback(confirmCallBackHandlerB);
        rabbitTemplate.setReturnCallback(returnCallBackHandlerB);
        //id + 时间戳 全局唯一
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString().replace("-", ""));
        rabbitTemplate.convertAndSend("springboot_topic_exchange", "springboot.info", message, correlationData);
    }

    /**
     * 采用单例的RabbitTemplate发送消息方法调用: 构建Message消息<br/>
     * 使用统一的confirm和return回调
     * @param message
     * @param properties
     * @throws Exception
     */
    public void singleSend(Object message, Map<String, Object> properties) throws Exception {
        //id + 时间戳 全局唯一
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString().replace("-", ""));
        singleRabbitTemplate.convertAndSend("springboot_direct_exchange", "direct_Key", message,correlationData);
    }
}

最后是我们的发送消息的test类

package com.wkp.springboot.producer;

import com.wkp.common.model.Order;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ProducerApplicationTests {

    @Resource
    private RabbitSender rabbitSender;
    @Resource
    private RabbitTemplate singleRabbitTemplate;

    @Test
    public void contextLoads() {
    }

    /**
     * 测试多例RabbitTemplate发送消息
     * @throws Exception
     */
    @Test
    public void test_scopeSendMsg() throws Exception {
        rabbitSender.scopeSend("多例测试111", null);
        rabbitSender.scopeSend("多例测试222", null);
        Thread.sleep(10000L);
    }

    /**
     * 测试单例RabbitTemplate发送消息
     * @throws Exception
     */
    @Test
    public void test_singleSendMsg() throws Exception {
        Order order = new Order(1L, "123456", "测试订单");
        rabbitSender.singleSend(order, new HashMap<>());
        Thread.sleep(10000L);
    }

    /**
     * 测试单例RabbitTemplate发送消息无法被路由
     * @throws Exception
     */
    @Test
    public void test_singleSendMsg_NoRoute() throws Exception {
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString().replace("-", ""));
        singleRabbitTemplate.convertAndSend("springboot_direct_exchange", "error_key", "无法路由的消息",correlationData);
    }
}

运行test,查看测试结果:

1、test_scopeSendMsg()测试多例的Bean发送消息,生产者控制台如下的日志:可以看到两次发送消息使用的都是不同的RabbitTemplate实例对象,confirm回调成功

获取新的RabbitTemplate:org.springframework.amqp.rabbit.core.RabbitTemplate@1b28f282
获取新的RabbitTemplate:org.springframework.amqp.rabbit.core.RabbitTemplate@411341bd
B-CONFIRM机制:消息投递成功:correlationId:a7186bec20a0466eaf8c6a56bc4bb823
B-CONFIRM机制:消息投递成功:correlationId:f6e39bc211fb4308ae8b72faf5b8c0e6

消费者的日志:

消费者B收到消息:多例测试111
消费者B收到消息:多例测试222

2、test_singleSendMsg()测试单例的Bean发送Order对象,生产者控制台可以看到如下日志:

A-CONFIRM机制:消息投递成功:correlationId:8311209d22384615afb638782d639caa

消费者日志

RabbitListener-消费者A收到消息:Order{orderId=1, orderNo='123456', orderDesc='测试订单'}

3、test_singleSendMsg_NoRoute()测试消息无法被路由,生产者日志:由于消息无法被路由到队列上,所以触发了return回调,但是因为交换机时存在的,所以confirm是成功的

A-RETURN机制:消息路由失败,msg:无法路由的消息,replyCode:312,replyText:NO_ROUTE,exchange:springboot_direct_exchange,routingKey:error_key
A-CONFIRM机制:消息投递成功:correlationId:17249482a37247b487945307f5e4f818

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值