欢迎光临
我们一直在努力

RabbitMQ SpringBoot消息队列与应用间通信

RabbitMQ 实战:Spring Boot 消息队列与应用间通信

上一篇已经整理过 RabbitMQ 的基础概念,所以这篇不再重复讲什么是生产者、消费者、队列、交换机,而是直接从实战角度,把 Spring Boot 版本的消息队列代码和不同项目之间通过 RabbitMQ 通信的写法梳理一遍。

本文主要包含两部分:

  • Spring Boot 整合 RabbitMQ,实现 work、fanout、direct、topic 几种常用模式。
  • 拆成两个 Spring Boot 项目,模拟订单系统下单后通知物流系统发货。
  • 示例默认使用本地 RabbitMQ:

    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 中添加依赖:

    <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:

    server:
    port: 8080

    spring:
    rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /

    也可以写成地址形式:

    spring:
    rabbitmq:
    addresses: amqp://guest:guest@127.0.0.1:5672/

    3. 定义常量

    先把所有队列名、交换机名集中放到一个常量类中,后面的配置类、生产者、消费者都直接引用它,避免字符串写错。

    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. 声明队列

    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. 编写生产者接口

    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. 编写消费者

    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()));
    }
    }

    访问:

    http://127.0.0.1:8080/producer/work?count=10

    可以看到两组监听方法交替消费消息。重点是:同一个队列中的一条消息,不会同时被两个监听器消费。

    三、Publish/Subscribe 发布订阅模式

    发布订阅模式使用 FanoutExchange,也就是广播交换机。生产者把消息发送给交换机,交换机会把消息转发给所有绑定到它的队列。

    它适合一条消息需要被多个业务同时处理的场景,比如订单创建后,同时通知短信服务、积分服务、优惠券服务。

    1. 声明队列、交换机和绑定关系

    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 中继续添加接口:

    @GetMapping("/fanout")
    public String fanout() {
    rabbitTemplate.convertAndSend(
    MqConstants.FANOUT_EXCHANGE,
    "",
    "hello spring boot: fanout"
    );
    return "fanout 消息发送成功";
    }

    fanout 交换机不关心 routingKey,所以第二个参数可以写空字符串。

    3. 编写消费者

    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);
    }
    }

    访问:

    http://127.0.0.1:8080/producer/fanout

    两个队列都会收到同一条消息:

    [fanout_queue1] 收到消息:hello spring boot: fanout
    [fanout_queue2] 收到消息:hello spring boot: fanout

    四、Routing 路由模式

    路由模式使用 DirectExchange。它会根据消息发送时携带的 routingKey,找到绑定关系中 routingKey 完全匹配的队列。

    这里用一个简单的颜色路由来演示:

    direct_queue1 绑定 orange
    direct_queue2 绑定 black、green

    发送 orange,只有队列 1 收到;发送 black 或 green,只有队列 2 收到。

    1. 声明队列、交换机和绑定关系

    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 消息

    @GetMapping("/direct")
    public String direct(@RequestParam String routingKey) {
    rabbitTemplate.convertAndSend(
    MqConstants.DIRECT_EXCHANGE,
    routingKey,
    "hello spring boot: direct " + routingKey
    );
    return "direct 消息发送成功,routingKey:" + routingKey;
    }

    3. 编写消费者

    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);
    }
    }

    测试接口:

    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

    预期结果:

    [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 支持通配符。

    常用通配符:

    * 表示匹配一个单词
    # 表示匹配零个或多个单词

    这里设计的绑定关系是:

    topic_queue1 绑定 *.error
    topic_queue2 绑定 #.info
    topic_queue2 绑定 *.error

    也就是说:

    order.error 可以匹配 *.error,所以两个队列都能收到
    pay.error 可以匹配 *.error,所以两个队列都能收到
    order.pay.info 可以匹配 #.info,所以 topic_queue2 能收到

    1. 声明队列、交换机和绑定关系

    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 消息

    @GetMapping("/topics")
    public String topics(@RequestParam String routingKey) {
    rabbitTemplate.convertAndSend(
    MqConstants.TOPIC_EXCHANGE,
    routingKey,
    "hello spring boot: topics " + routingKey
    );
    return "topic 消息发送成功,routingKey:" + routingKey;
    }

    3. 编写消费者

    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);
    }
    }

    测试:

    http://127.0.0.1:8080/producer/topics?routingKey=order.error
    http://127.0.0.1:8080/producer/topics?routingKey=order.pay.info

    预期日志:

    [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 的常用写法就整理完了。核心套路其实很固定:

    1. 引入 spring-boot-starter-amqp
    2. 配置 RabbitMQ 连接信息
    3. 声明 Queue、Exchange、Binding
    4. 生产者使用 RabbitTemplate 发送消息
    5. 消费者使用 @RabbitListener 监听队列

    六、基于 Spring Boot + RabbitMQ 完成应用通信

    接下来继续看应用间通信:RabbitMQ 不只是在一个项目内部传递消息,也可以作为不同应用之间的通信中间件。

    这里用一个常见的业务场景来演示:

    用户在订单系统下单成功后,订单系统发送消息。
    物流系统监听订单创建消息,收到消息后进行发货处理。

    在这个场景中:

    order-service 订单系统,生产者
    logistics-service 物流系统,消费者
    RabbitMQ 中间消息队列

    项目结构可以这样设计:

    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:

    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=/

    也可以使用地址形式:

    spring.rabbitmq.addresses=amqp://guest:guest@127.0.0.1:5672/

    2. 声明订单创建队列

    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. 编写下单接口

    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;
    }
    }

    访问订单系统接口:

    http://127.0.0.1:8080/order/createOrder

    只启动订单系统时,可以在 RabbitMQ 管理页面看到 order.create 队列中有消息堆积。等物流系统启动后,消息就会被消费。

    八、物流系统 logistics-service

    物流系统不需要知道订单系统的接口地址,也不需要订单系统主动调用它。它只需要监听 RabbitMQ 中的 order.create 队列。

    1. 配置文件

    logistics-service/src/main/resources/application.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. 编写监听器

    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("物流系统开始处理发货流程");
    }
    }

    启动两个服务后,再访问:

    http://127.0.0.1:8080/order/createOrder

    物流系统控制台会打印类似日志:

    物流系统收到订单消息:下单成功,订单ID:c0c25851-6fe0-49dd-bf93-0746677324321
    物流系统开始处理发货流程

    这就完成了两个不同 Spring Boot 项目之间的消息通信。

    这种写法的好处是,订单系统不需要等待物流系统处理完成,下单接口可以更快返回。即使物流系统临时重启,只要队列和消息配置得当,消息也可以先留在 RabbitMQ 中,等物流系统恢复后再继续消费。

    九、发送对象消息

    前面的例子发送的是字符串。如果业务中想发送一个订单对象,需要考虑序列化。Spring AMQP 推荐使用 JSON 消息转换器,比如 Jackson2JsonMessageConverter。

    在跨项目通信中,建议把消息对象放在公共模块中,或者至少保证两个项目中的 DTO 包名和字段一致。否则消费者反序列化时可能找不到对应类型。

    1. 定义订单消息对象

    可以在公共模块中定义,也可以在两个项目中保持相同包名:

    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,可以简化成:

    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 消息转换器

    订单系统和物流系统都建议添加这段配置。

    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. 订单系统发送对象

    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. 物流系统接收对象

    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 直接放到方法上:

    @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 写法:

    work:多个消费者监听同一个队列,同一条消息只被消费一次。
    fanout:广播给绑定到交换机的所有队列。
    direct:根据 routingKey 精确匹配队列。
    topic:根据 routingKey 通配符匹配队列。

    后半部分重点是理解应用间通信:

    订单系统不直接调用物流系统,而是把订单消息发送到 RabbitMQ。
    物流系统监听订单队列,收到消息后处理发货。

    这种方式可以降低系统之间的耦合,也能让一些不需要立即完成的业务异步执行。后续如果继续深入,还可以在这个基础上补充消息确认、失败重试、死信队列、延迟队列等高级特性。

    赞(0)
    未经允许不得转载:171主机测评 » RabbitMQ SpringBoot消息队列与应用间通信
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址