RabbitMQ 实战:Spring Boot 消息队列与应用间通信
上一篇已经整理过 RabbitMQ 的基础概念,所以这篇不再重复讲什么是生产者、消费者、队列、交换机,而是直接从实战角度,把 Spring Boot 版本的消息队列代码和不同项目之间通过 RabbitMQ 通信的写法梳理一遍。
本文主要包含两部分:
- Spring Boot 整合 RabbitMQ,实现 work、fanout、direct、topic 几种常用模式。
- 拆成两个 Spring Boot 项目,模拟订单系统下单后通知物流系统发货。
示例默认使用本地 RabbitMQ:
yaml
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
如果使用远程 RabbitMQ 服务器,把上面的连接信息替换成自己的地址、端口、用户名、密码和 virtual-host 即可。
一、Spring Boot 整合 RabbitMQ
Spring Boot 中操作 RabbitMQ 主要依赖 spring-boot-starter-amqp。它内部封装了 Spring AMQP,常用的发送工具是 RabbitTemplate,常用的消费方式是 @RabbitListener。
1. 引入依赖
创建 Spring Boot 项目时,勾选 Spring Web 和 Spring for RabbitMQ,也可以手动在 pom.xml 中添加依赖:
xml
<dependencies>
<!-- Web 接口,方便通过浏览器或 Postman 触发发送消息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring AMQP,整合 RabbitMQ -->
<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>
2. 添加 RabbitMQ 配置
application.yml:
yaml
server:
port: 8080
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
也可以写成地址形式:
yaml
spring:
rabbitmq:
addresses: amqp://guest:guest@127.0.0.1:5672/
3. 定义常量
先把所有队列名、交换机名集中放到一个常量类中,后面的配置类、生产者、消费者都直接引用它,避免字符串写错。
java
package com.example.rabbitmq.constant;
public class MqConstants {
private MqConstants() {
}
// work 模式
public static final String WORK_QUEUE = "work_queue";
// fanout 发布订阅模式
public static final String FANOUT_QUEUE1 = "fanout_queue1";
public static final String FANOUT_QUEUE2 = "fanout_queue2";
public static final String FANOUT_EXCHANGE = "fanout_exchange";
// direct 路由模式
public static final String DIRECT_QUEUE1 = "direct_queue1";
public static final String DIRECT_QUEUE2 = "direct_queue2";
public static final String DIRECT_EXCHANGE = "direct_exchange";
// topic 通配符模式
public static final String TOPIC_QUEUE1 = "topic_queue1";
public static final String TOPIC_QUEUE2 = "topic_queue2";
public static final String TOPIC_EXCHANGE = "topic_exchange";
}
二、Work Queues 工作队列模式
工作队列模式中,一个生产者往同一个队列发送多条消息,多个消费者监听同一个队列。多个消费者之间是竞争关系,同一条消息只会被其中一个消费者拿到。
1. 声明队列
java
package com.example.rabbitmq.config;
import com.example.rabbitmq.constant.MqConstants;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WorkQueueConfig {
@Bean
public Queue workQueue() {
return QueueBuilder.durable(MqConstants.WORK_QUEUE).build();
}
}
durable 表示队列持久化,RabbitMQ 重启后队列本身还会存在。注意,队列持久化不等于消息一定不丢,消息本身也要以持久化方式发送才更稳妥。
2. 编写生产者接口
java
package com.example.rabbitmq.controller;
import com.example.rabbitmq.constant.MqConstants;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/producer")
public class ProducerController {
private final RabbitTemplate rabbitTemplate;
public ProducerController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@GetMapping("/work")
public String work(@RequestParam(defaultValue = "10") Integer count) {
for (int i = 1; i <= count; i++) {
String message = "hello spring amqp: work message " + i;
// 使用默认交换机发送消息,routingKey 写队列名
rabbitTemplate.convertAndSend("", MqConstants.WORK_QUEUE, message);
}
return "work 消息发送成功,发送数量:" + count;
}
}
默认交换机是 RabbitMQ 内置交换机,发送时第一个参数写空字符串,第二个参数 routingKey 写队列名,消息就会被投递到对应队列。
3. 编写消费者
java
package com.example.rabbitmq.listener;
import com.example.rabbitmq.constant.MqConstants;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class WorkQueueListener {
@RabbitListener(queues = MqConstants.WORK_QUEUE)
public void listener1(Message message) {
System.out.println("listener1 [" + MqConstants.WORK_QUEUE + "] 收到消息:"
+ new String(message.getBody()));
}
@RabbitListener(queues = MqConstants.WORK_QUEUE)
public void listener2(Message message) {
System.out.println("listener2 [" + MqConstants.WORK_QUEUE + "] 收到消息:"
+ new String(message.getBody()));
}
}
访问:
text
http://127.0.0.1:8080/producer/work?count=10
可以看到两组监听方法交替消费消息。重点是:同一个队列中的一条消息,不会同时被两个监听器消费。
三、Publish/Subscribe 发布订阅模式
发布订阅模式使用 FanoutExchange,也就是广播交换机。生产者把消息发送给交换机,交换机会把消息转发给所有绑定到它的队列。
它适合一条消息需要被多个业务同时处理的场景,比如订单创建后,同时通知短信服务、积分服务、优惠券服务。
1. 声明队列、交换机和绑定关系
java
package com.example.rabbitmq.config;
import com.example.rabbitmq.constant.MqConstants;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutConfig {
@Bean
public Queue fanoutQueue1() {
return QueueBuilder.durable(MqConstants.FANOUT_QUEUE1).build();
}
@Bean
public Queue fanoutQueue2() {
return QueueBuilder.durable(MqConstants.FANOUT_QUEUE2).build();
}
@Bean
public FanoutExchange fanoutExchange() {
return ExchangeBuilder
.fanoutExchange(MqConstants.FANOUT_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding fanoutBinding1(@Qualifier("fanoutQueue1") Queue queue,
@Qualifier("fanoutExchange") FanoutExchange exchange) {
return BindingBuilder.bind(queue).to(exchange);
}
@Bean
public Binding fanoutBinding2(@Qualifier("fanoutQueue2") Queue queue,
@Qualifier("fanoutExchange") FanoutExchange exchange) {
return BindingBuilder.bind(queue).to(exchange);
}
}
2. 发送 fanout 消息
在前面的 ProducerController 中继续添加接口:
java
@GetMapping("/fanout")
public String fanout() {
rabbitTemplate.convertAndSend(
MqConstants.FANOUT_EXCHANGE,
"",
"hello spring boot: fanout"
);
return "fanout 消息发送成功";
}
fanout 交换机不关心 routingKey,所以第二个参数可以写空字符串。
3. 编写消费者
java
package com.example.rabbitmq.listener;
import com.example.rabbitmq.constant.MqConstants;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class FanoutListener {
@RabbitListener(queues = MqConstants.FANOUT_QUEUE1)
public void listenQueue1(String message) {
System.out.println("[" + MqConstants.FANOUT_QUEUE1 + "] 收到消息:" + message);
}
@RabbitListener(queues = MqConstants.FANOUT_QUEUE2)
public void listenQueue2(String message) {
System.out.println("[" + MqConstants.FANOUT_QUEUE2 + "] 收到消息:" + message);
}
}
访问:
text
http://127.0.0.1:8080/producer/fanout
两个队列都会收到同一条消息:
text
[fanout_queue1] 收到消息:hello spring boot: fanout
[fanout_queue2] 收到消息:hello spring boot: fanout
四、Routing 路由模式
路由模式使用 DirectExchange。它会根据消息发送时携带的 routingKey,找到绑定关系中 routingKey 完全匹配的队列。
这里用一个简单的颜色路由来演示:
text
direct_queue1 绑定 orange
direct_queue2 绑定 black、green
发送 orange,只有队列 1 收到;发送 black 或 green,只有队列 2 收到。
1. 声明队列、交换机和绑定关系
java
package com.example.rabbitmq.config;
import com.example.rabbitmq.constant.MqConstants;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DirectConfig {
@Bean
public Queue directQueue1() {
return QueueBuilder.durable(MqConstants.DIRECT_QUEUE1).build();
}
@Bean
public Queue directQueue2() {
return QueueBuilder.durable(MqConstants.DIRECT_QUEUE2).build();
}
@Bean
public DirectExchange directExchange() {
return ExchangeBuilder
.directExchange(MqConstants.DIRECT_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding directBindingOrange(@Qualifier("directQueue1") Queue queue,
@Qualifier("directExchange") DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("orange");
}
@Bean
public Binding directBindingBlack(@Qualifier("directQueue2") Queue queue,
@Qualifier("directExchange") DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("black");
}
@Bean
public Binding directBindingGreen(@Qualifier("directQueue2") Queue queue,
@Qualifier("directExchange") DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("green");
}
}
2. 发送 direct 消息
java
@GetMapping("/direct")
public String direct(@RequestParam String routingKey) {
rabbitTemplate.convertAndSend(
MqConstants.DIRECT_EXCHANGE,
routingKey,
"hello spring boot: direct " + routingKey
);
return "direct 消息发送成功,routingKey:" + routingKey;
}
3. 编写消费者
java
package com.example.rabbitmq.listener;
import com.example.rabbitmq.constant.MqConstants;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class DirectListener {
@RabbitListener(queues = MqConstants.DIRECT_QUEUE1)
public void listenQueue1(String message) {
System.out.println("[" + MqConstants.DIRECT_QUEUE1 + "] 收到消息:" + message);
}
@RabbitListener(queues = MqConstants.DIRECT_QUEUE2)
public void listenQueue2(String message) {
System.out.println("[" + MqConstants.DIRECT_QUEUE2 + "] 收到消息:" + message);
}
}
测试接口:
text
http://127.0.0.1:8080/producer/direct?routingKey=orange
http://127.0.0.1:8080/producer/direct?routingKey=black
http://127.0.0.1:8080/producer/direct?routingKey=green
预期结果:
text
[direct_queue1] 收到消息:hello spring boot: direct orange
[direct_queue2] 收到消息:hello spring boot: direct black
[direct_queue2] 收到消息:hello spring boot: direct green
如果发送一个没有绑定过的 routingKey,比如 blue,消息不会进入这两个队列。
五、Topics 通配符模式
通配符模式使用 TopicExchange。它和 direct 模式很像,区别是 direct 要求 routingKey 完全相等,而 topic 支持通配符。
常用通配符:
text
* 表示匹配一个单词
# 表示匹配零个或多个单词
这里设计的绑定关系是:
text
topic_queue1 绑定 *.error
topic_queue2 绑定 #.info
topic_queue2 绑定 *.error
也就是说:
text
order.error 可以匹配 *.error,所以两个队列都能收到
pay.error 可以匹配 *.error,所以两个队列都能收到
order.pay.info 可以匹配 #.info,所以 topic_queue2 能收到
1. 声明队列、交换机和绑定关系
java
package com.example.rabbitmq.config;
import com.example.rabbitmq.constant.MqConstants;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TopicConfig {
@Bean
public Queue topicQueue1() {
return QueueBuilder.durable(MqConstants.TOPIC_QUEUE1).build();
}
@Bean
public Queue topicQueue2() {
return QueueBuilder.durable(MqConstants.TOPIC_QUEUE2).build();
}
@Bean
public TopicExchange topicExchange() {
return ExchangeBuilder
.topicExchange(MqConstants.TOPIC_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding topicBindingError1(@Qualifier("topicQueue1") Queue queue,
@Qualifier("topicExchange") TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("*.error");
}
@Bean
public Binding topicBindingInfo(@Qualifier("topicQueue2") Queue queue,
@Qualifier("topicExchange") TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("#.info");
}
@Bean
public Binding topicBindingError2(@Qualifier("topicQueue2") Queue queue,
@Qualifier("topicExchange") TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("*.error");
}
}
2. 发送 topic 消息
java
@GetMapping("/topics")
public String topics(@RequestParam String routingKey) {
rabbitTemplate.convertAndSend(
MqConstants.TOPIC_EXCHANGE,
routingKey,
"hello spring boot: topics " + routingKey
);
return "topic 消息发送成功,routingKey:" + routingKey;
}
3. 编写消费者
java
package com.example.rabbitmq.listener;
import com.example.rabbitmq.constant.MqConstants;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class TopicListener {
@RabbitListener(queues = MqConstants.TOPIC_QUEUE1)
public void listenQueue1(String message) {
System.out.println("[" + MqConstants.TOPIC_QUEUE1 + "] 收到消息:" + message);
}
@RabbitListener(queues = MqConstants.TOPIC_QUEUE2)
public void listenQueue2(String message) {
System.out.println("[" + MqConstants.TOPIC_QUEUE2 + "] 收到消息:" + message);
}
}
测试:
text
http://127.0.0.1:8080/producer/topics?routingKey=order.error
http://127.0.0.1:8080/producer/topics?routingKey=order.pay.info
预期日志:
text
[topic_queue1] 收到消息:hello spring boot: topics order.error
[topic_queue2] 收到消息:hello spring boot: topics order.error
[topic_queue2] 收到消息:hello spring boot: topics order.pay.info
到这里,Spring Boot 整合 RabbitMQ 的常用写法就整理完了。核心套路其实很固定:
text
1. 引入 spring-boot-starter-amqp
2. 配置 RabbitMQ 连接信息
3. 声明 Queue、Exchange、Binding
4. 生产者使用 RabbitTemplate 发送消息
5. 消费者使用 @RabbitListener 监听队列
六、基于 Spring Boot + RabbitMQ 完成应用通信
接下来继续看应用间通信:RabbitMQ 不只是在一个项目内部传递消息,也可以作为不同应用之间的通信中间件。
这里用一个常见的业务场景来演示:
text
用户在订单系统下单成功后,订单系统发送消息。
物流系统监听订单创建消息,收到消息后进行发货处理。
在这个场景中:
text
order-service 订单系统,生产者
logistics-service 物流系统,消费者
RabbitMQ 中间消息队列
项目结构可以这样设计:
text
rabbitmq-communication
├── order-service
└── logistics-service
两个项目都引入 spring-boot-starter-web 和 spring-boot-starter-amqp。
七、订单系统 order-service
订单系统负责模拟下单。下单成功之后,它不直接调用物流系统接口,而是往 RabbitMQ 的 order.create 队列发送一条消息。
1. 配置文件
order-service/src/main/resources/application.properties:
properties
server.port=8080
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
也可以使用地址形式:
properties
spring.rabbitmq.addresses=amqp://guest:guest@127.0.0.1:5672/
2. 声明订单创建队列
java
package com.example.order.config;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
public static final String ORDER_CREATE_QUEUE = "order.create";
@Bean
public Queue orderCreateQueue() {
return QueueBuilder.durable(ORDER_CREATE_QUEUE).build();
}
}
这里使用默认交换机,所以只需要声明队列即可。后面发送消息时,routingKey 直接写队列名 order.create。
3. 编写下单接口
java
package com.example.order.controller;
import com.example.order.config.RabbitConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@RequestMapping("/order")
public class OrderController {
private final RabbitTemplate rabbitTemplate;
public OrderController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@GetMapping("/createOrder")
public String createOrder() {
// 真实业务中,这里会有参数校验、库存校验、保存订单等操作
String orderId = UUID.randomUUID().toString();
String message = "下单成功,订单ID:" + orderId;
// 使用默认交换机发送消息,routingKey 为队列名
rabbitTemplate.convertAndSend("", RabbitConfig.ORDER_CREATE_QUEUE, message);
return "下单成功,订单ID:" + orderId;
}
}
访问订单系统接口:
text
http://127.0.0.1:8080/order/createOrder
只启动订单系统时,可以在 RabbitMQ 管理页面看到 order.create 队列中有消息堆积。等物流系统启动后,消息就会被消费。
八、物流系统 logistics-service
物流系统不需要知道订单系统的接口地址,也不需要订单系统主动调用它。它只需要监听 RabbitMQ 中的 order.create 队列。
1. 配置文件
logistics-service/src/main/resources/application.properties:
properties
server.port=9090
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
订单系统占用了 8080 端口,所以物流系统改为 9090。
2. 编写监听器
java
package com.example.logistics.listener;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class OrderCreateListener {
@RabbitListener(queues = "order.create")
public void listenOrderCreate(String message) {
System.out.println("物流系统收到订单消息:" + message);
// 真实业务中,这里可以创建物流单、分配仓库、通知配送系统等
System.out.println("物流系统开始处理发货流程");
}
}
启动两个服务后,再访问:
text
http://127.0.0.1:8080/order/createOrder
物流系统控制台会打印类似日志:
text
物流系统收到订单消息:下单成功,订单ID:c0c25851-6fe0-49dd-bf93-0746677324321
物流系统开始处理发货流程
这就完成了两个不同 Spring Boot 项目之间的消息通信。
这种写法的好处是,订单系统不需要等待物流系统处理完成,下单接口可以更快返回。即使物流系统临时重启,只要队列和消息配置得当,消息也可以先留在 RabbitMQ 中,等物流系统恢复后再继续消费。
九、发送对象消息
前面的例子发送的是字符串。如果业务中想发送一个订单对象,需要考虑序列化。Spring AMQP 推荐使用 JSON 消息转换器,比如 Jackson2JsonMessageConverter。
在跨项目通信中,建议把消息对象放在公共模块中,或者至少保证两个项目中的 DTO 包名和字段一致。否则消费者反序列化时可能找不到对应类型。
1. 定义订单消息对象
可以在公共模块中定义,也可以在两个项目中保持相同包名:
java
package com.example.common.dto;
import java.io.Serializable;
public class OrderInfo implements Serializable {
private String orderId;
private String productName;
private Long price;
public OrderInfo() {
}
public OrderInfo(String orderId, String productName, Long price) {
this.orderId = orderId;
this.productName = productName;
this.price = price;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public Long getPrice() {
return price;
}
public void setPrice(Long price) {
this.price = price;
}
@Override
public String toString() {
return "OrderInfo{" +
"orderId='" + orderId + '\'' +
", productName='" + productName + '\'' +
", price=" + price +
'}';
}
}
如果项目中使用 Lombok,可以简化成:
java
package com.example.common.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderInfo implements Serializable {
private String orderId;
private String productName;
private Long price;
}
2. 配置 JSON 消息转换器
订单系统和物流系统都建议添加这段配置。
java
package com.example.order.config;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMessageConfig {
@Bean
public Jackson2JsonMessageConverter jackson2JsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory,
Jackson2JsonMessageConverter messageConverter) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(messageConverter);
return rabbitTemplate;
}
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
ConnectionFactory connectionFactory,
Jackson2JsonMessageConverter messageConverter) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setMessageConverter(messageConverter);
return factory;
}
}
如果在两个项目中都使用这段配置,消费者监听方法就可以直接接收 OrderInfo 对象。
3. 订单系统发送对象
java
package com.example.order.controller;
import com.example.common.dto.OrderInfo;
import com.example.order.config.RabbitConfig;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@RequestMapping("/order")
public class OrderController {
private final RabbitTemplate rabbitTemplate;
public OrderController(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
}
@GetMapping("/createOrderObject")
public String createOrderObject() {
String orderId = UUID.randomUUID().toString();
OrderInfo orderInfo = new OrderInfo(orderId, "机械键盘", 53600L);
rabbitTemplate.convertAndSend("", RabbitConfig.ORDER_CREATE_QUEUE, orderInfo);
return "下单成功,已发送订单对象:" + orderId;
}
}
这里的 price 使用 Long,单位可以约定为分,这样可以避免小数金额精度问题。
4. 物流系统接收对象
java
package com.example.logistics.listener;
import com.example.common.dto.OrderInfo;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
@RabbitListener(queues = "order.create")
public class OrderCreateObjectListener {
@RabbitHandler
public void handleOrderCreate(OrderInfo orderInfo) {
System.out.println("物流系统收到订单对象:" + orderInfo);
System.out.println("订单ID:" + orderInfo.getOrderId());
System.out.println("商品名称:" + orderInfo.getProductName());
System.out.println("商品金额:" + orderInfo.getPrice());
}
}
也可以把 @RabbitListener 直接放到方法上:
java
@Component
public class OrderCreateObjectListener {
@RabbitListener(queues = "order.create")
public void handleOrderCreate(OrderInfo orderInfo) {
System.out.println("物流系统收到订单对象:" + orderInfo);
}
}
@RabbitListener 用来声明监听哪个队列,@RabbitHandler 用来标记具体处理消息的方法。当一个监听类中有多个处理方法时,@RabbitHandler 更常用。
十、完整测试顺序
建议按照下面的顺序测试:
- 先启动单项目 Spring Boot 示例,访问
/producer/work,观察两个监听器竞争消费。 - 访问
/producer/fanout,观察两个 fanout 队列都收到同一条消息。 - 访问
/producer/direct?routingKey=orange、black、green,观察消息按 routingKey 精确路由。 - 访问
/producer/topics?routingKey=order.error和/producer/topics?routingKey=order.pay.info,观察通配符匹配效果。 - 再启动
order-service,访问/order/createOrder,先确认订单系统能发送消息。 - 启动
logistics-service,再次访问订单接口,观察物流系统能消费订单消息。 - 最后改成对象消息,配置 JSON 转换器,再测试
/order/createOrderObject。
总结
Spring Boot 整合 RabbitMQ 之后,代码比原生 RabbitMQ Java API 更简洁。发送消息主要靠 RabbitTemplate,接收消息主要靠 @RabbitListener。
前半部分重点是掌握不同模式的 Spring 写法:
text
work:多个消费者监听同一个队列,同一条消息只被消费一次。
fanout:广播给绑定到交换机的所有队列。
direct:根据 routingKey 精确匹配队列。
topic:根据 routingKey 通配符匹配队列。
后半部分重点是理解应用间通信:
text
订单系统不直接调用物流系统,而是把订单消息发送到 RabbitMQ。
物流系统监听订单队列,收到消息后处理发货。
这种方式可以降低系统之间的耦合,也能让一些不需要立即完成的业务异步执行。后续如果继续深入,还可以在这个基础上补充消息确认、失败重试、死信队列、延迟队列等高级特性。