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?
你看到的消费者注解里配置 exchange 和 routingKey,并不是消费者监听队列的必需条件 ,而是 @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 这个交换机和队列的路由关系)。
这是「声明资源」的需求,不是「消费消息」的需求。
总结
- 生产者 :必须知道
exchange + routingKey(消息路由的必要条件),不需要知道 queue; - 消费者 :核心只需要知道
queue(监听队列拿消息),exchange和routingKey只是「自动声明绑定关系」时的附加配置,不是必需; @QueueBinding里的routingKey是「交换机和队列的绑定规则」,不是消费者的消费规则 ------ 只要队列已存在,消费者完全可以忽略 exchange 和 routingKey。
生产环境建议:提前在 RabbitMQ 控制台 / 运维脚本创建好交换机、队列、绑定关系,消费者只通过 @RabbitListener(queues = "xxx") 监听队列,这样代码最简洁、耦合度最低。