注解实现rabbitmq消费者和生产者

1. 引入依赖(Maven)

首先在 pom.xml 中添加 RabbitMQ 依赖:

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.7.15</version> <!-- 稳定版本,可根据需要调整 -->
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>rabbitmq-annotation-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Spring Boot RabbitMQ 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!-- Spring Boot Web 依赖(用于测试发送消息) -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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>

2. 配置文件(application.yml)

XML 复制代码
spring:
  # RabbitMQ 配置
  rabbitmq:
    host: localhost  # RabbitMQ 服务地址
    port: 5672       # 端口号
    username: guest  # 用户名(默认guest)
    password: guest  # 密码(默认guest)
    virtual-host: /  # 虚拟主机(默认/)
    # 消费者配置
    listener:
      simple:
        acknowledge-mode: auto  # 自动确认消息(可根据需要改为manual手动确认)
        concurrency: 1          # 最小并发消费者数
        max-concurrency: 10     # 最大并发消费者数

3. 生产者代码

XML 复制代码
package com.example.rabbitmq.producer;

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

/**
 * RabbitMQ 生产者(注解式)
 */
@Component
public class RabbitMQProducer {

    // Spring 提供的 RabbitMQ 模板类,用于发送消息
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送消息到指定交换机和路由键
     * @param exchange 交换机名称
     * @param routingKey 路由键
     * @param message 消息内容
     */
    public void sendMessage(String exchange, String routingKey, String message) {
        try {
            // 发送消息
            rabbitTemplate.convertAndSend(exchange, routingKey, message);
            System.out.println("生产者发送消息成功:" + message);
        } catch (Exception e) {
            System.err.println("生产者发送消息失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
}

4. 消费者代码(核心注解)

XML 复制代码
package com.example.rabbitmq.consumer;

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

/**
 * RabbitMQ 消费者(注解式)
 * 核心注解:@RabbitListener - 监听队列
 *          @QueueBinding - 绑定交换机、队列、路由键
 *          @Queue - 声明队列
 *          @Exchange - 声明交换机
 */
@Component
public class RabbitMQConsumer {

    /**
     * 消费者1:监听指定队列(自动声明交换机、队列、绑定关系)
     * @param message 接收到的消息
     */
    @RabbitListener(
            bindings = @QueueBinding(
                    // 声明队列:name=队列名,durable=true(持久化),autoDelete=false(不自动删除)
                    value = @Queue(name = "demo.queue", durable = "true", autoDelete = "false"),
                    // 声明交换机:name=交换机名,type=DIRECT(直连交换机),durable=true(持久化)
                    exchange = @Exchange(name = "demo.exchange", type = "direct", durable = "true"),
                    // 路由键
                    key = "demo.routing.key"
            )
    )
    // @RabbitHandler 注解用于指定处理消息的方法(单个方法时可省略)
    @RabbitHandler
    public void receiveMessage(String message) {
        try {
            System.out.println("消费者接收到消息:" + message);
            // 这里编写业务逻辑,比如处理消息、入库等
            // 如果需要手动确认消息,需配置 acknowledge-mode=manual,并调用 channel.basicAck()
        } catch (Exception e) {
            System.err.println("消费者处理消息失败:" + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 消费者2:监听已存在的队列(不声明交换机/队列,直接绑定)
     * 适用于交换机和队列已提前创建的场景
     */
    @RabbitListener(queues = "existing.queue")
    public void receiveExistingQueueMessage(String message) {
        System.out.println("消费者2接收到已有队列的消息:" + message);
    }
}

5. 测试类(用于发送消息)

XML 复制代码
package com.example.rabbitmq;

import com.example.rabbitmq.producer.RabbitMQProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * 启动类 + 测试发送消息
 */
@SpringBootApplication
public class RabbitMQAnnotationDemoApplication implements CommandLineRunner {

    @Autowired
    private RabbitMQProducer rabbitMQProducer;

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

    @Override
    public void run(String... args) throws Exception {
        // 启动项目后自动发送测试消息
        rabbitMQProducer.sendMessage("demo.exchange", "demo.routing.key", "Hello RabbitMQ! (注解式)");
    }
}

上面的代码里生产者需要知道exchange和routingkey,消费者除queue外,为什么也要知道exchange和routingkey?

一、先理清 RabbitMQ 的核心路由逻辑(关键前提)

RabbitMQ 的消息流转路径是:生产者Exchange(交换机) → (根据 routingKey 路由)→ Queue(队列)消费者

这个路径里,不同角色关注的信息完全不同,这是理解的核心:

  • 生产者:只负责把消息「交给交换机」,并告诉交换机「用哪个 routingKey 路由」------ 它不需要知道最终消息会到哪个队列(解耦的关键)。
  • 消费者:只负责「从队列里拿消息」------ 它本质上只需要知道「监听哪个队列」,至于这个队列和哪个交换机、哪个 routingKey 绑定,对消费者来说「不是必需的」(除非要自动声明绑定关系)。

二、为什么代码里消费者注解会出现 exchange + routingKey?

你看到的消费者注解里配置 exchangeroutingKey并不是消费者监听队列的必需条件 ,而是 @QueueBinding 注解的「附加功能」导致的 ------ @QueueBinding 不只是「监听队列」,还包含了「自动声明交换机、队列,并建立它们的绑定关系」。

1. 先看两种消费者写法的对比(核心区别)
写法 1:只监听已存在的队列(消费者只需要知道 queue,最常用)

这是生产环境中最常见的写法(队列 / 交换机已提前由运维创建),消费者完全不需要知道 exchange 和 routingKey:

复制代码
@Component
public class RabbitMQConsumer {
    // 只监听队列,不需要任何 exchange/routingKey ------ 这才是消费者的核心逻辑
    @RabbitListener(queues = "demo.queue")
    public void receiveMessage(String message) {
        System.out.println("消费者接收到消息:" + message);
    }
}

这种写法下,消费者只关注「demo.queue」这个队列,至于这个队列和哪个 exchange、哪个 routingKey 绑定,消费者完全不关心,也不需要知道。

写法 2:用 @QueueBinding 自动声明绑定关系(需要 routingKey)

你之前看到的注解写法,是「监听队列 + 自动声明交换机 / 队列 / 绑定关系」的组合操作:

复制代码
@RabbitListener(
    bindings = @QueueBinding(
        value = @Queue(name = "demo.queue"), // 声明队列
        exchange = @Exchange(name = "demo.exchange"), // 声明交换机
        key = "demo.routing.key" // 声明绑定关系的 routingKey
    )
)

这里的 key = "demo.routing.key" 不是给「消费者」用的,而是给「RabbitMQ 服务器」用的 ------ 告诉 RabbitMQ:「把 demo.exchange 交换机中 routingKey 为 demo.routing.key 的消息,路由到 demo.queue 队列」。

简单说:routingKey 是「交换机和队列之间的绑定规则」,不是「消费者和队列之间的规则」。

三、回答你的核心问题

1. 为什么生产者需要知道 exchange 和 routingKey?

生产者的目标是「把消息送到正确的队列」,但它不直接操作队列(解耦),而是通过交换机转发:

  • 必须指定 exchange:消息要发给哪个交换机(相当于「快递中转站」);
  • 必须指定 routingKey:告诉交换机「按什么规则转发」(相当于「快递地址的详细街道」);
  • 生产者不需要知道队列:比如同一个 exchange + routingKey,可能对应多个队列(广播场景),生产者无需关心。
2. 消费者不知道 routingKey 行不行?知道 exchange 和 queue 就够了么?
  • 不知道 routingKey 完全可以:消费者的核心是「监听队列」,只要队列存在,哪怕不知道 routingKey 和 exchange,也能正常消费;
  • 知道 exchange 也不是必需的:只有在「需要通过注解自动声明绑定关系」时,才需要指定 exchange;如果队列已存在,只需要知道 queue 就行(这是最优解)。

甚至可以说:消费者最好只知道 queue,不要耦合 exchange 和 routingKey ------ 这样队列和交换机的绑定关系变化时,消费者代码完全不需要改,符合「开闭原则」。

四、补充:什么时候消费者需要配 exchange/routingKey?

只有一种场景需要:你希望在代码里自动创建交换机、队列,并建立它们的绑定关系(比如开发环境快速初始化)。此时:

  • @Exchange:声明交换机(告诉 RabbitMQ 创建这个交换机);
  • @Queue:声明队列(告诉 RabbitMQ 创建这个队列);
  • key:声明绑定规则(告诉 RabbitMQ 这个交换机和队列的路由关系)。

这是「声明资源」的需求,不是「消费消息」的需求。

总结

  1. 生产者 :必须知道 exchange + routingKey(消息路由的必要条件),不需要知道 queue;
  2. 消费者 :核心只需要知道 queue(监听队列拿消息),exchangeroutingKey 只是「自动声明绑定关系」时的附加配置,不是必需;
  3. @QueueBinding 里的 routingKey 是「交换机和队列的绑定规则」,不是消费者的消费规则 ------ 只要队列已存在,消费者完全可以忽略 exchange 和 routingKey。

生产环境建议:提前在 RabbitMQ 控制台 / 运维脚本创建好交换机、队列、绑定关系,消费者只通过 @RabbitListener(queues = "xxx") 监听队列,这样代码最简洁、耦合度最低。

相关推荐
IT 行者9 小时前
Spring Security 7 OAuth2 授权码分布式存储之Redis存储方案
redis·分布式·spring
潇凝子潇10 小时前
kafka之监控告警
分布式·kafka
Light6011 小时前
从“报告”到“能力”——构建智能化、可审计的数据治理闭环——领码 SPARK 数据质量平台白皮书
大数据·分布式·spark
maozexijr11 小时前
RabbitMQ Exchange Headers类型存在的意义?
分布式·rabbitmq
还在忙碌的吴小二11 小时前
XXL-SSO 分布式单点登录框架
分布式
独自破碎E11 小时前
RabbitMQ的消息确认机制是怎么工作的?
分布式·rabbitmq
潇凝子潇12 小时前
Kafka 实现集群安全认证与加密机制
分布式·安全·kafka
潇凝子潇12 小时前
Apache Kafka 跨集群复制实现方案
分布式·kafka·apache
Li_76953213 小时前
Redis 进阶(八)—— 分布式锁
数据库·redis·分布式
Justice Young13 小时前
Flume笔记:Flume的基本介绍和使用
大数据·分布式·flume