RabbitMQ笔记(二)SpringBoot整合RabbitMQ之simple容器(生产者)

目录

    • 一、简介
    • 二、Maven依赖
    • 三、消息确认
      • 3.1 消息发布确认ConfirmCallback
      • 3.2 消息到达确认ReturnsCallback
    • 四、核心配置类
    • 五、配置文件
    • 六、测试类TestProductService
    • 结语

一、简介

  本文主要用使用Spring Boot(2.5.2)来整合RabbitMQ(2.5.2),使用simple容器实现一个生产者。本文的前提是有一个安装好的RabbitMQ的环境,及我的上一篇文章里消费者服务:
  链接: RabbitMQ笔记(一)SpringBoot整合RabbitMQ之simple容器(消费者).

二、Maven依赖

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>

三、消息确认

3.1 消息发布确认ConfirmCallback

  通过实现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
  • NONE:禁用发布确认模式,默认值
  • CORRELATED:发布消息成功到交换机你后会触发回调方法
  • SIMPLE:发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果

3.2 消息到达确认ReturnsCallback

  通过实现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

六、测试类TestProductService

  之前我说过,测试生产者和消费者时,最好使用两个系统测试,不然很多问题你会觉得很奇怪,比如序列化问题。
现在我生产者则可以加入测试包,进行各种类型的测试,也是对@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就到这里了,认真研读我这两篇文件,结合我的实例,相信你会有收获的,如果有什么疑问也可以评论交流。

你可能感兴趣的:(RabbitMQ笔记,java,spring,boot,rabbitmq)