本文主要用使用Spring Boot(2.5.2)来整合RabbitMQ(2.5.2),使用simple容器实现一个生产者。本文的前提是有一个安装好的RabbitMQ的环境,及我的上一篇文章里消费者服务:
链接: RabbitMQ笔记(一)SpringBoot整合RabbitMQ之simple容器(消费者).
pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.2version>
<relativePath/>
parent>
<groupId>com.aliangroupId>
<artifactId>publishartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>publishname>
<description>SpringBoot整合RabbitMQ之simple容器(生产者)description>
<properties>
<java.version>1.8java.version>
<jackson.version>2.9.10jackson.version>
<spring.version>5.3.8spring.version>
<junit.version>4.12junit.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<version>${parent.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>${parent.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-testartifactId>
<version>${parent.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
<version>${parent.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.68version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>${jackson.version}version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatypegroupId>
<artifactId>jackson-datatype-jsr310artifactId>
<version>${jackson.version}version>
dependency>
<dependency>
<groupId>com.aliangroupId>
<artifactId>commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.16version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
这里需要注意的是下面这个包,是我本人打包到私服的,其实一个员工类,支付类,加上一个常量类,在我上一篇文章里也提过,就不多说了。
<dependency>
<groupId>com.aliangroupId>
<artifactId>commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
通过实现RabbitTemplate.ConfirmCallback接口,消息发送到 Broker 后触发回调,确认消息是否到达 Broker 服务器,也就是:消息正确到达Exchange(交换机)中就触发。这里你也可以可以利用CorrelationData和redis等实现相关的业务逻辑,比如你创建CorrelationData对象时定义一个唯一的key,并关联业务数据,然后回调时你可以取出数据继续其他的业务。
ConfirmCallBackService.java
package com.alian.publish.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class ConfirmCallBackService implements RabbitTemplate.ConfirmCallback{
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (!ack){
//消息发送失败
System.out.println("消息发送失败,原因为:" + cause);
//可以利用CorrelationData和redis等实现相关的业务逻辑
//dosomething();
return;
}
//消息发送成功
System.out.println("消息发送成功");
}
}
想要你的ConfirmCallback配置生效,还得在配置文件中增加如下配置
#发送消息的确认模式有三种 none,correlated,SIMPLE
spring.rabbitmq.publisher-confirm-type=correlated
通过实现RabbitTemplate.ReturnsCallback接口,启动消息失败返回,比如:消息路由不到队列时就会触发回调。这个时候你可能要重新发送或者丢弃消息,又或是配合死信队列继续业务处理。
ReturnCallBackService.java
package com.alian.publish.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class ReturnCallBackService implements RabbitTemplate.ReturnsCallback {
@Override
public void returnedMessage(ReturnedMessage returnedMessage) {
log.info("消息失败返回状态码:{}", returnedMessage.getReplyCode());
log.info("消息失败返回消息文本:{}", returnedMessage.getReplyText());
log.info("消息失败返回交换机:{}", returnedMessage.getExchange());
log.info("消息失败返回路由:{}", returnedMessage.getRoutingKey());
log.info("消息失败返回消息内容:{}", returnedMessage.getMessage());
//dosomething();
}
}
想要你的ReturnsCallback配置生效,还得在配置文件中增加如下配置
#启用消息发布失败回调,默认关闭(比如从交换机路由不到到队列后触发)
spring.rabbitmq.publisher-returns=true
那么怎么使用呢?只需要通过注解@Autowired引入,具体的可以看下一小节。
@Autowired
private ConfirmCallBackService confirmCallBackService;
@Autowired
private ReturnCallBackService returnCallBackService;
SimpleRabbitMqConfig.java
package com.alian.publish.config;
import com.alian.publish.service.ConfirmCallBackService;
import com.alian.publish.service.ReturnCallBackService;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
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.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
@Configuration
public class SimpleRabbitMqConfig {
@Autowired
private ConfirmCallBackService confirmCallBackService;
@Autowired
private ReturnCallBackService returnCallBackService;
@Bean(name = "simpleContainerFactory")
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory simpleContainerFactory = new SimpleRabbitListenerContainerFactory();
//she设置工厂
simpleContainerFactory.setConnectionFactory(connectionFactory);
//发送消息使用Jackson2JsonMessageConverter序列化
simpleContainerFactory.setMessageConverter(this.jackson2JsonMessageConverter());
//消费者listener抛出异常,是否重回队列,默认true:重回队列, false为不重回队列(结合死信交换机)
simpleContainerFactory.setDefaultRequeueRejected(false);
return simpleContainerFactory;
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate();
//she设置工厂
rabbitTemplate.setConnectionFactory(connectionFactory);
//发送消息使用Jackson2JsonMessageConverter序列化(支持java 8时间)
rabbitTemplate.setMessageConverter(this.jackson2JsonMessageConverter());
//设置receive()方法的超时时间
rabbitTemplate.setReceiveTimeout(5000L);
//sendAndReceive()方法的超时时间,默认5000毫秒
rabbitTemplate.setReplyTimeout(30000L);
//设置消息发布确认回调(消息发送到交换机就会触发)
rabbitTemplate.setConfirmCallback(confirmCallBackService);
//设置消息发布失败回调(比如从交换机路由不到到队列后触发)
rabbitTemplate.setReturnsCallback(returnCallBackService);
//Mandatory为true时,消息通过交换器无法匹配到队列会返回给生产者,为false时匹配不到会直接被丢弃
rabbitTemplate.setMandatory(true);
return rabbitTemplate;
}
@Bean("jacksonMessageConverter")
public MessageConverter jackson2JsonMessageConverter() {
ObjectMapper mapper = getMapper();
return new Jackson2JsonMessageConverter(mapper);
}
/**
* 使用com.fasterxml.jackson.databind.ObjectMapper
* 对数据进行处理包括java8里的时间
*
* @return
*/
private ObjectMapper getMapper() {
ObjectMapper mapper = new ObjectMapper();
//设置可见性
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//默认键入对象
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//设置Java 8 时间序列化
JavaTimeModule timeModule = new JavaTimeModule();
timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
//禁用把时间转为时间戳
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//遇到未知属性或者属性不匹配的时候不抛出异常
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.registerModule(timeModule);
return mapper;
}
}
application.properties
#项目名和端口
server.port=6666
server.servlet.context-path=/publish
#RabbitMQ配置
#地址
spring.rabbitmq.addresses=192.168.0.194
#端口
spring.rabbitmq.port=5672
#用户名
spring.rabbitmq.username=test
#密码
spring.rabbitmq.password=test
#连接到代理时用的虚拟主机
spring.rabbitmq.virtual-host=/
#发送消息的确认模式
# NONE:禁用发布确认模式,默认值
# CORRELATED:发布消息成功到交互器你后会触发回调方法
# SIMPLE:发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果
spring.rabbitmq.publisher-confirm-type=correlated
#启用消息失败回调(比如从交换机路由不到到队列后触发)
spring.rabbitmq.publisher-returns=true
之前我说过,测试生产者和消费者时,最好使用两个系统测试,不然很多问题你会觉得很奇怪,比如序列化问题。
现在我生产者则可以加入测试包,进行各种类型的测试,也是对@RabbitListener和@RabbitHandler使用的一个更深的理解,具体参考下例。
TestProductService.java
package com.alian.publish.service;
import com.alian.common.constant.MQConstants;
import com.alian.common.dto.Employee;
import com.alian.common.dto.PayRecord;
import com.alibaba.fastjson.JSONObject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestProductService {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void sendStrMsg() {
String msg = "这是我发送的RabbitMQ测试消息";
rabbitTemplate.convertAndSend(MQConstants.ALIAN_EXCHANGE_NAME, MQConstants.ALIAN_ROUTINGKEY_NAME, msg);
}
@Test
public void sendJsonMsg() {
JSONObject json = new JSONObject();
json.put("author", "Alian");
json.put("blog", "https://blog.csdn.net/Alian_1223");
json.put("title", "RabbitMQ笔记(一)SpringBoot整合RabbitMQ(simple容器)");
rabbitTemplate.convertAndSend(MQConstants.ALIAN_EXCHANGE_NAME, MQConstants.ALIAN_ROUTINGKEY_NAME, json);
}
@Test
public void sendHashMapMsg() {
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("content", "RabbitMQ");
hashMap.put("tips", "RabbitMQ中simple容器的整合");
hashMap.put("publishTime", LocalDateTime.now());
rabbitTemplate.convertAndSend(MQConstants.ALIAN_EXCHANGE_NAME, MQConstants.ALIAN_ROUTINGKEY_NAME, hashMap);
}
@Test
public void sendEmployeeMsg() {
CorrelationData correlationData = new CorrelationData("WTSD147258");
MessagePostProcessor processor = message -> {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
};
Employee employee = new Employee();
employee.setId("WTSD147258");
employee.setName("唐鹏");
employee.setAge(30);
employee.setSalary(20000.0);
employee.setDepartment("研发部");
employee.setHireDate(LocalDate.of(2015, 3, 20));
rabbitTemplate.convertAndSend(MQConstants.ALIAN_EXCHANGE_NAME, MQConstants.ALIAN_ROUTINGKEY_NAME, employee, processor, correlationData);
}
@Test
public void sendPayRecordMsg() {
CorrelationData correlationData = new CorrelationData("2021072514723658965");
MessagePostProcessor processor = message -> {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
};
PayRecord payRecord = new PayRecord();
payRecord.setPayTranSeq("2021072514723658965");
payRecord.setPayAmount(10000);
payRecord.setPayType("01");
payRecord.setStatus("00");
payRecord.setPayTime(LocalDateTime.now());
payRecord.setPayNo("420125836485665587452255");
rabbitTemplate.convertAndSend(MQConstants.ALIAN_EXCHANGE_NAME, MQConstants.ALIAN_ROUTINGKEY_NAME, payRecord, processor, correlationData);
//rabbitTemplate.convertAndSend(MQConstants.ALIAN_EXCHANGE_NAME, "xxx", payRecord, processor, correlationData);
}
@Test
public void routingNotFound() {
String msg = "我发送的消息,此消息的路由key是不存在的!!!";
rabbitTemplate.convertAndSend(MQConstants.ALIAN_EXCHANGE_NAME, "routingNotFound", msg);
}
}
最后我得提醒下,很多小伙伴可能按照我这个执行的时候会出现如下的错误:
消息发送失败,原因为:clean channel shutdown; protocol method: #method<channel.close>(reply-code=200, reply-text=OK, class-id=0, method-id=0)
这是因为ConfirmCallback是异步的,我们使用junit测试发送完消息后就关闭了,也就断开了连接,所以测试时候可以加入一个休眠代码,或者采用@PostConstruct进行测试。
@Test
public void routingNotFound() {
String msg = "我发送的消息,此消息的路由key是不存在的!!!";
rabbitTemplate.convertAndSend(MQConstants.ALIAN_EXCHANGE_NAME, "routingNotFound", msg);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
或者
@PostConstruct
public void routingNotFound() {
String msg = "我发送的消息,此消息的路由key是不存在的!!!";
rabbitTemplate.convertAndSend(MQConstants.ALIAN_EXCHANGE_NAME, "routingNotFound", msg);
}
字符串发送运行结果:
2021-08-02 14:18:36 129 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2] INFO processStr 52:----------开始处理String----------
2021-08-02 14:18:36 129 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2] INFO processStr 53:接收到的字符串信息: 这是我发送的RabbitMQ测试消息
2021-08-02 14:18:36 129 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2] INFO processStr 54:----------String处理完成----------
json发送运行结果:
2021-08-02 14:21:16 616 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] INFO processJson 61:----------开始处理JSONObject----------
2021-08-02 14:21:16 616 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] INFO processJson 62:接收到的JSONObject信息: {"author":"Alian","blog":"https://blog.csdn.net/Alian_1223","title":"RabbitMQ笔记(一)SpringBoot整合RabbitMQ(simple容器)"}
2021-08-02 14:21:16 616 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] INFO processJson 63:----------JSONObject处理完成----------
HashMap发送运行结果:
2021-08-02 14:22:03 623 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2] INFO processHashMap 70:----------开始处理HashMap----------
2021-08-02 14:22:03 624 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2] INFO processHashMap 71:接收到的HashMap信息: {publishTime=2021-08-02T14:22:03, content=RabbitMQ, tips=RabbitMQ中simple容器的整合}
2021-08-02 14:22:03 624 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-2] INFO processHashMap 72:----------HashMap处理完成----------
Employee对象发送运行结果:
2021-08-02 14:23:04 818 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] INFO processEmployee 27:----------开始处理Employee----------
2021-08-02 14:23:04 818 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] INFO processEmployee 28:接收到的Employee信息: Employee{id='WTSD147258', name='唐鹏', age=30, salary=20000.0, department='研发部', hireDate=2015-03-20}
2021-08-02 14:23:04 818 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] INFO processEmployee 29:----------Employee处理完成----------
PayRecord 送运行结果:
2021-08-02 14:24:46 652 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] INFO processPayRecord 40:----------开始处理PayRecord----------
2021-08-02 14:24:46 652 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] INFO processPayRecord 41:接收到的PayRecord信息: PayRecord{payTranSeq='2021080214723658965', payAmount=10000, payType='01', status='00', payTime=2021-08-02T14:24:46, payNo='420125836485665587452255'}
2021-08-02 14:24:46 653 [org.springframework.amqp.rabbit.RabbitListenerEndpointContainer#0-1] INFO processPayRecord 42:----------PayRecord处理完成----------
消息确认测试,我们发送一个不存在的路由key可以看到ConfirmCallback和ReturnsCallback都触发了:
消息发送成功
2021-08-02 14:25:34 640 [rabbitConnectionFactory1] INFO returnedMessage 14:消息失败返回状态码:312
2021-08-02 14:25:34 642 [rabbitConnectionFactory1] INFO returnedMessage 15:消息失败返回消息文本:NO_ROUTE
2021-08-02 14:25:34 642 [rabbitConnectionFactory1] INFO returnedMessage 16:消息失败返回交换机:ALIAN_EXCHANGE
2021-08-02 14:25:34 642 [rabbitConnectionFactory1] INFO returnedMessage 17:消息失败返回路由:routingNotFound
2021-08-02 14:25:34 642 [rabbitConnectionFactory1] INFO returnedMessage 18:消息失败返回消息内容:(Body:'"我发送的消息,此消息的路由key是不存在的!!!"' MessageProperties [headers={__TypeId__=java.lang.String}, contentType=application/json, contentEncoding=UTF-8, contentLength=0, receivedDeliveryMode=PERSISTENT, priority=0, deliveryTag=0])
本次的Spring Boot整合RabbitMQ就到这里了,认真研读我这两篇文件,结合我的实例,相信你会有收获的,如果有什么疑问也可以评论交流。