本节将会介绍一下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