Spring Boot中集成RabbitMQ
in JavaDevelop with 0 comment

Spring Boot中集成RabbitMQ

in JavaDevelop with 0 comment

介绍

假设这样的情况,一个网络商场的一台服务器每秒能处理 40 单下单请求,但是商场搞促销活动的时候,短短几个小时间每秒的下单请求可能能到 400 单,虽然可以通过把服务器增加到十台这样的方式来处理高峰期时间的下单请求,但是平时也维持 10 台服务器的话却会大大增加运营成本。在这种情况下,消息中间件的作用就会被体现出来。

RabbitMQ 是一种应用非常广泛的开源消息中间件,接下来将学习如何在 SpringBoot 中使用 RabbitMQ。

通过代码示例演示基于直接队列模式、分列模式、主题模式、延迟队列的消息发送和接收。

RabbitMQ 工作原理

关于 RabbitMQ,可以用一张图来概括它的工作模式:

image-1655346081933

可以看到,生产者是消息推送者,发送消息到 RabbitMQ 服务器中,经过交换机、队列后,最终发送到消费者,也就是消息接收者。

RabbitMQ 中交换机的概念和计算机网络中网络层的路由器概念其实非常相似,它一边可以从多个生产者那接受消息,另外一边可以将消息推送到队列。交换机必须知道如何处理它接受的消息,是应该推送到指定队列还是推送到多个队列。

常用的交换机有以下三种:

  1. Direct Exchange:直连型交换机,是默认的交换机类型。它的工作原理是将消息推送到路由键匹配的队列中,关于路由键的设会在后面的实例演示中讲解。

  2. Fanout Exchange:扇形交换机,这个交换机没有路由键的概念,它会将消息广播到所有附加到这个交换机的队列上。使用这种交换机,可以完成消息的发布/订阅。

  3. Topic Exchange:主题交换机,它与扇形交换机类似,但是,它可以使用 routingKey —— 路由键进行消息匹配,使得消费者可以灵活地订阅想要接受的主题的消息。routingKey 使用下面的字符匹配消息:

    • * 匹配一个分段(用 “.” 分割)的内容
    • # 匹配 0 和多个字符

    具体使用,在接下来的演示中大家会了解到。

有了交换机,就会有对应的消息发送模式,RabbitMQ 官网介绍了六种消息发送模式:RabbitMQ Tutorials

这样,就可以总结出每种模式对应需要使用的交换机:

消息模式 交换机
简单消息模式 空交换机
发布/订阅模式 扇形交换机
路由模式 直连交换机
主题模式 主题交换机

接下来就是我们的实例演示环节。

安装 RabbitMQ

自 Debian since 6.0 (squeeze) 和 Ubuntu 9.04 之后,rabbitmq-server 就在 Ubuntu 的官方源里面了,但是如果你想安装最新版本,可以在官网下载,或者使用官方提供的源来安装。本次实验我们就用 Ubuntu 源里的版本安装,所有的依赖都可以被自动安装。

sudo apt-get update
sudo apt-get install rabbitmq-server

启动 RabbitMQ

当 RabbitMQ 安装完毕的时候服务器就会像后台程序一般运行起来了。

服务器相关命令:

启动:sudo service rabbitmq-server start
关闭: sudo service rabbitmq-server stop
查看状态:sudo service rabbitmq-server status

新建项目

由于 maven 下载包需要联网,这里先将 maven 包下载下来,放到本地仓库便可以解决联网的问题

wget http://labfile.oss.aliyuncs.com/courses/1152/m2.zip
unzip m2.zip
mv .m2 /home/shiyanlou/

首先需要新建一个 SpringBoot 项目,具体步骤可以查看前面的章节,这里不再赘述。

mvn archetype:generate -DgroupId=com.shiyanlou -DartifactId=rabbit -DarchetypeArtifactId=maven-archetype-quickstart

最后形成下面的目录结构:

image-1655346102520

添加依赖

在使用 Spring 发送和接收 RabbitMQ 消息之前,需要将 Spring Boot 的 AMQP starter 依赖添加到构建文件中,这个依赖会创建一个 AMQP 连接工厂和 RabbitTemplate bean.

Spring Boot 整合 RabbitMQ 只要添加这项依赖就可以了:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

修改 pom.xml,添加下面的依赖

<?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.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.shiyanlou</groupId>
    <artifactId>rabbit</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rabbit</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

        </plugins>
    </build>

</project>

配置 RabbitMQ

第一种

当需要把应用程序投入生产环境时,就需要认证的 RabbitMQ 代理,它运行在本地机器上并监听 5672 端口。所以需要一个配置文件来配置这些属性。

src/main/resources 建立一个 YAML 配置文件 application.yml ,并添加如下的配置信息。

server:
  port: 8080
  servlet:
    context-path: /example
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    # 手动提交ack消息
    listener:
      simple:
        acknowledge-mode: manual
      direct:
        acknowledge-mode: manual

guest 是 RabbitMQ 代理默认的账号和密码。

这样,RabbitMQ 就整合进了 Spring Boot 环境中。

第二种

在src/main/resources中的 application.properties 配置文件中添加以下配置

# rabbitmq地址
spring.rabbitmq.host=localhost
# 用户名
spring.rabbitmq.username=guest
# 密码
spring.rabbitmq.password=guest
# 端口
spring.rabbitmq.port=5672

创建常量池

接下来,将演示多种发送消息的模式,在 com.example.mq.rabbitmq.constants 下先创建一个 RabbitMQ 常量池 RabbitConsts.java ,我们将使用这个常量池,规定一些队列、交换机和路由键的名称:

package com.example.mq.rabbitmq.constants;

/**
 * RabbitMQ 常量池
 */
public interface RabbitConsts {
    /**
     * 直接模式1
     */
    String DIRECT_MODE_QUEUE_ONE = "queue.direct.1";

    /**
     * 队列2
     */
    String QUEUE_TWO = "queue.2";

    /**
     * 队列3
     */
    String QUEUE_THREE = "3.queue";

    /**
     * 分列模式
     */
    String FANOUT_MODE_QUEUE = "fanout.mode";

    /**
     * 主题模式
     */
    String TOPIC_MODE_QUEUE = "topic.mode";

    /**
     * 路由1
     */
    String TOPIC_ROUTING_KEY_ONE = "queue.#";

    /**
     * 路由2
     */
    String TOPIC_ROUTING_KEY_TWO = "*.queue";

    /**
     * 路由3
     */
    String TOPIC_ROUTING_KEY_THREE = "3.queue";
}

可以看到路由键 1 和路由键 2 分别为 queue.#*.queue,而队列 2 和队列 3 分别为 queue.23.queue 并将其作为组件添加进 Spring Boot 项目的时候,对应的路由和队列就会进行匹配。

创建配置类

在上一小节,使用常量池的形式规定了一些队列,主题和路由键的名称,这一节,就需要配置对应的方法,并且使用 @Bean 对其注释,使其作为组件能够添加进 Spring Boot 中。

配置队列,实现如下方法向容器中添加一个 queueName 组件,形式如下:

    @Bean
    public Queue queueName() {
        return new Queue("队列名");
    }

同理,对应的交换机也使用返回 FanoutExchange 类和 TopicExchange 类的方法来完成:

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("交换机名");
    }

    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange("交换机名");
    }

最后还需要配置一个返回 RabbitTemplate 类的方法,Spring 对 RabbitMQ 消息支持的核心是 RabbitTemplate ,它是一个简化消息访问的助手类。具体实现形式如下:

    @Bean
    public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory){
        // RabbitTemplate使用CachingConnectionFactory作为连接工厂
        // 使用完整的(相关的)publisher确认,并使用相关数据和每个消息的返回
        connectionFactory.setPublisherConfirms(true);
        connectionFactory.setPublisherReturns(true);
        // 构造器
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        // 在发送消息时设置强制标识,使其返回消息发送成功或丢失的消息
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause));
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message));
        return rabbitTemplate;
    }

最后 com.example.mq.rabbitmq.config 中配置类文件 RabbitMqConfig.java 的完整代码如下:

package com.example.mq.rabbitmq.config;

import com.example.mq.rabbitmq.constants.RabbitConsts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * RabbitMQ 配置类
 */
@Slf4j
@Configuration
public class RabbitMqConfig {

    @Bean
    public RabbitTemplate rabbitTemplate(CachingConnectionFactory connectionFactory){
        // RabbitTemplate使用CachingConnectionFactory作为连接工厂
        // 使用完整的(相关的)publisher确认,并使用相关数据和每个消息的返回
        connectionFactory.setPublisherConfirms(true);
        connectionFactory.setPublisherReturns(true);
        // 构造器
        RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
        // 在发送消息时设置强制标识,使其返回消息发送成功或丢失的消息
        rabbitTemplate.setMandatory(true);
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> log.info("消息发送成功:correlationData({}),ack({}),cause({})", correlationData, ack, cause));
        rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> log.info("消息丢失:exchange({}),route({}),replyCode({}),replyText({}),message:{}", exchange, routingKey, replyCode, replyText, message));
        return rabbitTemplate;
    }

    /**
     * 直接模式队列1
     */
    @Bean
    public Queue directOneQueue() {
        return new Queue(RabbitConsts.DIRECT_MODE_QUEUE_ONE);
    }

    /**
     * 队列2
     */
    @Bean
    public Queue queueTwo() {
        return new Queue(RabbitConsts.QUEUE_TWO);
    }

    /**
     * 队列3
     */
    @Bean
    public Queue queueThree() {
        return new Queue(RabbitConsts.QUEUE_THREE);
    }

    /**
     * 分列模式队列
     */
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(RabbitConsts.FANOUT_MODE_QUEUE);
    }

    /**
     * 分列模式绑定队列1
     */
    @Bean
    public Binding fanoutBinding1(Queue directOneQueue, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(directOneQueue).to(fanoutExchange);
    }

    /**
     * 分列模式绑定队列2
     */
    @Bean
    public Binding fanoutBinding2(Queue queueTwo, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(queueTwo).to(fanoutExchange);
    }

    /**
     * 主题模式队列
     * <li>路由格式必须以 . 分隔,比如 user.email 或者 user.aaa.email</li>
     * <li>通配符 * ,代表一个占位符,或者说一个单词,比如路由为 user.*,那么 user.email 可以匹配,但是 user.aaa.email 就匹配不了</li>
     * <li>通配符 # ,代表一个或多个占位符,或者说一个或多个单词,比如路由为 user.#,那么 user.email 可以匹配,user.aaa.email 也可以匹配</li>
     */
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(RabbitConsts.TOPIC_MODE_QUEUE);
    }


    /**
     * 主题模式绑定分列模式
     */
    @Bean
    public Binding topicBinding1(FanoutExchange fanoutExchange, TopicExchange topicExchange) {
        return BindingBuilder.bind(fanoutExchange).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_ONE);
    }

    /**
     * 主题模式绑定队列2
     */
    @Bean
    public Binding topicBinding2(Queue queueTwo, TopicExchange topicExchange) {
        return BindingBuilder.bind(queueTwo).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_TWO);
    }

    /**
     * 主题模式绑定队列3
     */
    @Bean
    public Binding topicBinding3(Queue queueThree, TopicExchange topicExchange) {
        return BindingBuilder.bind(queueThree).to(topicExchange).with(RabbitConsts.TOPIC_ROUTING_KEY_THREE);
    }
}

可以看到这里使用了一个比较陌生的注解:

编写消息处理类

在我们的讲解中,仅讲解 com.example.mq.rabbitmq.handler 下直接模式的代码 DirectQueueOneHandler.java ,其它的代码几乎一样。

当消息抵达 RabbitMQ 的队列中时,某个方法应该被调用,这个方法用于进行消息的监听,在消息被消费者消费以后,进行消息确认。对这样的方法,添加 @RabbitListener(queues = 队列名) 注解。队列名是这个方法所监听的消息队列的名称。具体代码如下:

package com.example.mq.rabbitmq.handler;

import com.example.mq.rabbitmq.constants.RabbitConsts;
import com.example.mq.rabbitmq.message.MessageStruct;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j
@RabbitListener(queues = RabbitConsts.DIRECT_MODE_QUEUE_ONE)
@Component
public class DirectQueueOneHandler {

    // @RabbitHandler
    public void directHandlerAutoAck(MessageStruct message) {
        log.info("直接队列处理器,接收消息:{}", message.getMessage());
    }

    @RabbitHandler
    public void directHandlerManualAck(MessageStruct messageStruct, Message message, Channel channel) {
        //  如果手动ACK,消息会被监听消费,但是消息在队列中依旧存在,如果 未配置 acknowledge-mode 默认是会在消费完毕后自动ACK掉
        final long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            log.info("直接队列1,手动ACK,接收消息:{}", messageStruct.getMessage());
            // 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(deliveryTag, false);
        } catch (IOException e) {
            try {
                // 处理失败,重新压入MQ
                channel.basicRecover();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
}

RabbitMQConfig

在src/main/java/com/shiyanlou/中新建RabbitConfig用来配置 RabbitMQ,比如创建队列,创建 Exchange 等。这里我们采用发布/订阅模式,所以我们建立了一个 Exchange 和两个队列。

package com.shiyanlou;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @author shiyanlou
 */
@Component
public class RabbitConfig {

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

    @Bean
    public Queue queueB() {
        return new Queue("queueB");
    }

    /**
     * 建立一个fanout模式的Exchange
     *
     * @return
     */
    @Bean
    FanoutExchange fanoutExchange() {
        return new FanoutExchange("fanoutExchange");
    }

    /**
     * 绑定队列A到交换机
     *
     * @param queueA
     * @param fanoutExchange
     * @return
     */
    @Bean
    public Binding bindingQueueA(Queue queueA, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(queueA).to(fanoutExchange);
    }

    /**
     * 绑定队列A到交换机
     *
     * @param queueB
     * @param fanoutExchange
     * @return
     */
    @Bean
    public Binding bindingQueueB(Queue queueB, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(queueB).to(fanoutExchange);
    }

}

创建提供者

在src/main/java/com/shiyanlou/中新建Provider用于生产消息

package com.shiyanlou;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author shiyanlou
 */
@Component
public class Provider {

    @Autowired
    private AmqpTemplate amqpTemplate;

    public void sendMessage(String msg){
        System.out.println("提供者 发送消息:" + msg);
        amqpTemplate.convertAndSend("fanoutExchange","", msg);
    }
}

创建消费者

在src/main/java/com/shiyanlou/中新建Consumer用于消费消息

package com.shiyanlou;

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @author shiyanlou
 */
@Component
public class Consumer {

    @RabbitListener(queues = "queueA")
    public void receiveQueueA(String msg){
        System.out.println("消费者 queueA 收到消息:" + msg);
    }

    @RabbitListener(queues = "queueB")
    public void receiveQueueB(String msg){
        System.out.println("消费者 queueB 收到消息:" + msg);
    }
}

添加 Spring Boot 启动类

第一种

com.example.mq.rabbitmq 下添加启动类 RabbitmqdemoApplication.java

package com.example.mq.rabbitmq;

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

@SpringBootApplication
public class RabbitmqdemoApplication {

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

}

启动类在后面的实验中,除了类名和 package 不一致,其他部分完全相同,我们就不特别去讲解。同学们在坐后面的实验时,记得自行修改启动类。

第二种

在src/main/java/com/shiyanlou新建 SpringBoot 启动类RabbitApplication.java

package com.shiyanlou;

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

@SpringBootApplication
public class RabbitApplication {

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

}

单元测试

第一种

到目前为止,已经将所需的所有配置配置完毕了,接下来就可以向 RabbitMQ 中发送消息了。RabbitMQ 发送消息使用的是 RabbitTemplate 中的 send()converAndSend() 方法。在此列举出一些用法:

// 发送原始的消息
void send(Message message) throws AmqpException;
void send(String routingKey, Message message) throws AmqpException;
void send(String exchange, String routingKey, Message message)
                    throws AmqpException;

// 发送根据对象转换而成的消息
void convertAndSend(Object message) throws AmqpException;
void convertAndSend(String routingKey, Object message)
                    throws AmqpException;
void convertAndSend(String exchange, String routingKey,
                    Object message) throws AmqpException;

可以看到,前三个 send() 方法直接发送了原始的 Message 对象, 后面三个 convertAndSend() 方法接受了一个对象,然后在后台将其转换为 Message 对象。

在此直接演示 convertAndSend() 方法的使用。配置一个非 Message 的消息对象类 MessageStruct ,代码如下:

package com.example.mq.rabbitmq.message;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MessageStruct implements Serializable {
    private static final long serialVersionUID = 392365881428311040L;

    private String message;

}

这里也出现了的 Lombok 的几个注解,分别作一些解释:

com.example.mq.rabbitmq 中新建一个测试类 RabbitmqdemoApplicationTests.java 来发送消息,具体代码如下:

package com.example.mq.rabbitmq;

import com.example.mq.rabbitmq.constants.RabbitConsts;
import com.example.mq.rabbitmq.message.MessageStruct;
import org.junit.Test;
import org.junit.runner.RunWith;
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;

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

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 测试直接模式发送
     */
    @org.junit.Test
    public void sendDirect() {
        rabbitTemplate.convertAndSend(RabbitConsts.DIRECT_MODE_QUEUE_ONE, new MessageStruct("direct message"));
    }

    /**
     * 测试分列模式发送
     */
    @org.junit.Test
    public void sendFanout() {
        rabbitTemplate.convertAndSend(RabbitConsts.FANOUT_MODE_QUEUE, "", new MessageStruct("fanout message"));
    }

    /**
     * 测试主题模式发送1
     */
    @org.junit.Test
    public void sendTopic1() {
        rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "queue.aaa.bbb", new MessageStruct("topic message"));
    }

    /**
     * 测试主题模式发送2
     */
    @org.junit.Test
    public void sendTopic2() {
        rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "ccc.queue", new MessageStruct("topic message"));
    }

    /**
     * 测试主题模式发送3
     */
    @org.junit.Test
    public void sendTopic3() {
        rabbitTemplate.convertAndSend(RabbitConsts.TOPIC_MODE_QUEUE, "3.queue", new MessageStruct("topic message"));
    }
}

然后输入以下的命令,运行测试类:

cd spring-boot-mq-rabbitmq
mvn test

最终可以看到程序正确运行的结果,在测试类中发送的消息成功地被监听器捕获到了:

image-1655346147745

第二种

一切准备就绪后,我们需要编写单元测试,来检查是否程序是否可以正常运行。在·src/test/java下新建RabbitApplicationTests

import com.shiyanlou.Provider;
import com.shiyanlou.RabbitApplication;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RabbitApplication.class)
public class RabbitApplicationTests {

    @Autowired
    private Provider provider;

    @Test
    public void providerTest() {
        provider.sendMessage("Hello Shiyanlou");
    }

}

运行单元测试

cd /home/project/rabbit
mvn test

image-1655346155403