RabbitMq(五) -- SpringBoot整合 RabbitMQ 完整实现

1. 概述

RabbitMQ 是基于 AMQP 协议的消息中间件,在分布式系统中常用于解耦、异步处理、流量削峰等场景。SpringBoot 通过 spring-boot-starter-amqp 提供了对 RabbitMQ 的深度整合,让开发者能够以声明式或编程式的方式轻松操作消息队列。

本文将详细讲解:

  • 队列创建的两种方式:@Bean 方式与 AmqpAdmin 方式

  • 消息接收的两种方式:@RabbitListener 与 @RabbitHandler

每个知识点均配有详细的文字解释和完整的代码示例,参数含义会逐一说明。

2. 环境准备

2.1 添加依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

2.2 配置连接信息

yaml 复制代码
spring:
  rabbitmq:
    host: 127.0.0.1       # RabbitMQ 服务地址
    port: 5672            # 连接端口(注意不是管理界面端口 15672)
    username: guest       # 用户名
    password: guest       # 密码
    virtual-host: /       # 虚拟主机,默认为 /
    # 可选:连接超时、重试等配置
    connection-timeout: 5000
    publisher-confirm-type: correlated   # 开启发送确认
    publisher-returns: true               # 开启消息返回

3. 队列创建的两种方式

3.1 方式一:使用 @Bean 声明式创建

通过在 @Configuration 类中定义 Queue、Exchange、Binding 的 Bean,Spring 容器启动时会自动在 RabbitMQ 服务端创建这些组件。

3.1.1 基本示例

java 复制代码
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQBeanConfig {

    // 1. 创建队列
    @Bean
    public Queue myQueue() {
        // 参数详解:
        // name: 队列名称,必须唯一
        // durable: 是否持久化(true: 重启后队列依然存在)
        // exclusive: 是否独占(true: 仅当前连接可用,连接关闭后队列删除)
        // autoDelete: 是否自动删除(最后一个消费者取消后删除队列)
        // 还有另一种构造方式:new Queue(name, durable, exclusive, autoDelete, arguments)
        return new Queue("myQueue", true, false, false);
    }

    // 2. 创建交换机(以 DirectExchange 为例)
    @Bean
    public DirectExchange myExchange() {
        // 参数详解:
        // name: 交换机名称
        // durable: 是否持久化
        // autoDelete: 是否自动删除
        // arguments: 扩展参数
        return new DirectExchange("myExchange", true, false);
    }

    // 3. 创建绑定关系
    @Bean
    public Binding myBinding() {
        return BindingBuilder
            .bind(myQueue())                // 绑定哪个队列
            .to(myExchange())               // 绑定到哪个交换机
            .with("myRoutingKey");          // 路由键
    }
}

3.1.2 带参数的队列(死信队列、TTL 等)

java 复制代码
@Bean
public Queue ttlQueue() {
    Map<String, Object> args = new HashMap<>();
    // 设置队列中消息的 TTL(毫秒)
    args.put("x-message-ttl", 60000);
    // 设置死信交换机
    args.put("x-dead-letter-exchange", "deadLetterExchange");
    // 设置死信路由键
    args.put("x-dead-letter-routing-key", "deadLetterKey");
    // 设置队列最大长度(条数)
    args.put("x-max-length", 1000);
    // 设置队列最大容量(字节)
    args.put("x-max-length-bytes", 10485760);
    return new Queue("ttlQueue", true, false, false, args);
}

3.2 方式二:使用 AmqpAdmin 动态创建

AmqpAdmin 是 Spring 提供的用于管理 RabbitMQ 组件的接口,可以在运行时动态声明队列、交换机和绑定关系,适合需要根据业务逻辑动态创建临时队列的场景。

3.2.1 AmqpAdmin 常用方法

java 复制代码
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DynamicQueueService {

    @Autowired
    private AmqpAdmin amqpAdmin;

    // 动态创建队列
    public void createQueue(String queueName, boolean durable, boolean exclusive, boolean autoDelete) {
        Queue queue = new Queue(queueName, durable, exclusive, autoDelete);
        amqpAdmin.declareQueue(queue);
    }

    // 动态创建交换机
    public void createExchange(String exchangeName, String type, boolean durable) {
        AbstractExchange exchange;
        switch (type) {
            case "direct":
                exchange = new DirectExchange(exchangeName, durable, false);
                break;
            case "topic":
                exchange = new TopicExchange(exchangeName, durable, false);
                break;
            case "fanout":
                exchange = new FanoutExchange(exchangeName, durable, false);
                break;
            case "headers":
                exchange = new HeadersExchange(exchangeName, durable, false);
                break;
            default:
                throw new IllegalArgumentException("不支持的类型: " + type);
        }
        amqpAdmin.declareExchange(exchange);
    }

    // 动态创建绑定
    public void createBinding(String queueName, String exchangeName, String routingKey) {
        Binding binding = new Binding(
            queueName,
            Binding.DestinationType.QUEUE,
            exchangeName,
            routingKey,
            null  // arguments
        );
        amqpAdmin.declareBinding(binding);
    }

    // 删除队列
    public boolean deleteQueue(String queueName) {
        return amqpAdmin.deleteQueue(queueName);
    }

    // 清空队列消息(不删除队列)
    public void purgeQueue(String queueName) {
        amqpAdmin.purgeQueue(queueName, false);
    }
}

3.2.2 动态创建带参数的队列

java 复制代码
public void createQueueWithArgs(String queueName) {
    Map<String, Object> args = new HashMap<>();
    args.put("x-message-ttl", 30000);        // 30秒过期
    args.put("x-max-priority", 10);          // 支持优先级
    Queue queue = new Queue(queueName, true, false, false, args);
    amqpAdmin.declareQueue(queue);
}

3.3 两种方式的对比:

4. 消息发送(RabbitTemplate)

在讲解消息接收之前,先了解如何发送消息,以便测试接收功能。

java 复制代码
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MessageSender {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 简单发送字符串
    public void send(String exchange, String routingKey, String message) {
        rabbitTemplate.convertAndSend(exchange, routingKey, message);
    }

    // 发送带消息属性的消息
    public void sendWithProperties(String exchange, String routingKey, String message) {
        MessageProperties properties = new MessageProperties();
        properties.setContentType("text/plain");
        properties.setPriority(5);              // 优先级
        properties.setExpiration("60000");      // 消息过期时间(毫秒)
        properties.setHeader("customKey", "customValue");
        Message msg = MessageBuilder.withBody(message.getBytes(StandardCharsets.UTF_8))
                                    .andProperties(properties)
                                    .build();
        rabbitTemplate.send(exchange, routingKey, msg);
    }

    // 发送 Java 对象(需要配置 Jackson 转换器)
    public void sendObject(String exchange, String routingKey, Object obj) {
        rabbitTemplate.convertAndSend(exchange, routingKey, obj);
    }
}

5. 消息接收的两种方式

5.1 方式一:@RabbitListener 注解

@RabbitListener 可以标注在方法或类上,用于声明一个消息监听容器,自动监听指定队列。

5.1.1 基础用法

java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class BasicListener {

    // 监听单个队列,自动 ACK(默认)
    @RabbitListener(queues = "myQueue")
    public void listenSimple(String message) {
        System.out.println("收到消息: " + message);
    }

    // 监听多个队列
    @RabbitListener(queues = {"queueA", "queueB"})
    public void listenMultiple(String message) {
        System.out.println("从多个队列收到: " + message);
    }

    // 使用路由键动态创建队列(需要交换机已存在)
    @RabbitListener(
        bindings = @QueueBinding(
            value = @Queue(value = "autoQueue", durable = "true"),
            exchange = @Exchange(value = "myExchange", type = ExchangeTypes.DIRECT),
            key = "myRoutingKey"
        )
    )
    public void listenWithBinding(String message) {
        System.out.println("通过 @QueueBinding 自动创建: " + message);
    }
}

5.1.2 @RabbitListener 常用参数详解

5.1.3 手动确认消息(ACK)

默认情况下,消息消费成功后会自动 ACK。若需要手动确认,需配置 acknowledge-mode 为 MANUAL,并在监听方法中引入 Channel 和 deliveryTag。

java 复制代码
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class ManualAckListener {

    @RabbitListener(queues = "myQueue", containerFactory = "manualAckContainerFactory")
    public void listenManual(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        try {
            // 业务处理
            System.out.println("处理消息: " + new String(message.getBody()));
            // 手动确认(单条)
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            // 拒绝并重新入队(第二个参数 true 表示重新入队)
            channel.basicNack(deliveryTag, false, true);
        }
    }
}

需要配置 containerFactory:

java 复制代码
@Bean
public RabbitListenerContainerFactory<?> manualAckContainerFactory(
        ConnectionFactory connectionFactory) {
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory);
    factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 手动确认
    return factory;
}

5.2 方式二:@RabbitListener + @RabbitHandler 组合

当一个队列中包含多种类型(或格式)的消息时,可以在类上标注 @RabbitListener,然后在多个方法上标注 @RabbitHandler,根据消息类型自动路由到对应方法。

5.2.1 根据消息类型分发

java 复制代码
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(queues = "multiTypeQueue")
public class MultiTypeListener {

    // 处理 String 类型
    @RabbitHandler
    public void handleString(String message) {
        System.out.println("字符串消息: " + message);
    }

    // 处理 Integer 类型
    @RabbitHandler
    public void handleInteger(Integer number) {
        System.out.println("整数消息: " + number);
    }

    // 处理自定义 User 对象(需要配置 Jackson 转换器)
    @RabbitHandler
    public void handleUser(User user) {
        System.out.println("用户对象: " + user.getName() + ", " + user.getAge());
    }

    // 处理 byte[] 类型(原始消息体)
    @RabbitHandler
    public void handleBytes(byte[] bytes) {
        System.out.println("字节消息: " + new String(bytes));
    }

    // 处理 Message 原始对象(包含属性)
    @RabbitHandler
    public void handleRawMessage(Message message) {
        String body = new String(message.getBody());
        String contentType = message.getMessageProperties().getContentType();
        System.out.println("原始消息 - 类型: " + contentType + ", 内容: " + body);
    }
}

5.2.2 @RabbitHandler 方法参数匹配规则

  • 根据方法参数的 Java 类型进行匹配(需要 MessageConverter 能将消息体转换成对应类型)
  • 如果存在多个匹配的方法,会按照最具体的类型进行选择
  • 可以通过 @Payload 和 @Headers 注解精确控制
java 复制代码
@RabbitHandler
public void handleWithAnnotations(@Payload User user, @Headers Map<String, Object> headers) {
    System.out.println("Payload: " + user);
    System.out.println("Headers: " + headers);
}

5.2.3 配置 Jackson 消息转换器(支持对象)

java 复制代码
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConverterConfig {

    @Bean
    public MessageConverter jacksonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}

配置后,发送对象时会自动转为 JSON,接收时会自动反序列化为对应的 Java 类型。

6. 完整示例:综合运用

6.1 配置类(使用 @Bean 创建队列)

java 复制代码
@Configuration
public class FullConfig {

    @Bean
    public Queue orderQueue() {
        return new Queue("order.queue", true);
    }

    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange("order.exchange", true, false);
    }

    @Bean
    public Binding orderBinding() {
        return BindingBuilder.bind(orderQueue())
                .to(orderExchange())
                .with("order.create");
    }
}

6.2 发送者

java 复制代码
@Service
public class OrderSender {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void sendOrder(Order order) {
        rabbitTemplate.convertAndSend("order.exchange", "order.create", order);
    }
}

6.3 接收者(混合使用两种接收方式)

java 复制代码
@Component
public class OrderListener {

    // 方式一:简单接收
    @RabbitListener(queues = "order.queue")
    public void handleOrder(Order order) {
        System.out.println("收到订单: " + order.getOrderId());
    }

    // 方式二:多类型处理器
    @Component
    @RabbitListener(queues = "order.queue")
    public static class MultiHandler {
        @RabbitHandler
        public void onOrder(Order order) {
            System.out.println("Handler 处理订单: " + order.getOrderId());
        }
        @RabbitHandler
        public void onString(String str) {
            System.out.println("意外收到字符串: " + str);
        }
    }
}

7. 常见问题与注意事项

  • 队列是否存在:使用 @RabbitListener 时,若队列不存在且没有 bindings 自动创建,启动会报错。建议使用 @Bean 预先声明或 bindings 属性。

  • 消息转换器:如果发送和接收使用不同的消息转换器,可能导致类型转换异常。全局统一配置 MessageConverter 为 Jackson2JsonMessageConverter 可避免大部分问题。

  • 并发消费:concurrency 参数可以增加消费者实例,提高吞吐量,但要注意业务幂等性。

  • 消息确认:生产环境建议使用手动 ACK,避免业务异常导致消息丢失。

  • 死信队列:务必为重要业务队列配置死信交换机,防止消息堆积或异常丢失后无法追溯。

8. 总结

本文详细介绍了 SpringBoot 整合 RabbitMQ 的核心操作:

  • 队列创建:@Bean 方式适合固定结构,AmqpAdmin 适合动态需求

  • 消息接收:@RabbitListener 简单直接,配合 @RabbitHandler 可处理多类型消息

相关推荐
huanmieyaoseng10032 小时前
SpringBoot使用Redis缓存
java·spring boot·后端
QC·Rex2 小时前
Spring Boot + Spring AI 实战:从零构建企业级 AI 应用
spring boot·大模型·向量数据库·rag·spring ai·tool calling
白露与泡影2 小时前
Spring Boot 缓存架构:一行配置切换 Caffeine 与 Redis,透明支持多租户隔离
spring boot·缓存·架构
indexsunny3 小时前
互联网大厂Java面试实战:从Spring Boot到微服务架构的技术问答
java·spring boot·redis·微服务·面试·kafka·spring security
计算机学姐4 小时前
基于SpringBoot的在线学习网站平台【个性化推荐+数据可视化+课程章节学习】
java·vue.js·spring boot·后端·学习·mysql·信息可视化
星晨雪海4 小时前
Spring Boot 常用注解
java·spring boot·后端
二进制person4 小时前
JavaEE进阶 --Spring Framework、Spring Boot和Spring Web MVC(3)
spring boot·spring·java-ee
xufengzhu4 小时前
Springboot项目信创选型
java·spring boot·后端
Arva .4 小时前
RabbitMQ消费者处理失败
分布式·rabbitmq