【Rabbit基础篇】声明组件与 JSON 消息转换器深度解析

🔥个人主页:代码不加冰(欢迎来访)

🎬作者简介:java后端学习者

❄️个人专栏:LeetCode刷题日记 ,苍穹外卖日记SSM框架深入JavaWeb

命运的结局尽可永在,不屈的挑战却不可须臾或缺!


前言:

大家好我是代码不加冰,在这里祝大家端午安康,可惜在学校吃不到老家的粽子(只有蜜枣的那种)不知道有没有懂得,还是挺想念的。闲话就不多说了,由于是期末周,重心还是应对期末考试,我们这里主要是接着前面的RabbitMQ消息队列继续深入,忙着别的专题搁置了一段时间,我们继续拾起来,同样的还有算法和八股,都要带着学习了。


摘要:

本文介绍了RabbitMQ消息队列在Java中的两种主流声明方式:通过Spring AMQP(配置类或注解)和原生Java客户端。重点讲解了Spring AMQP的配置方法,包括交换机、队列的声明及绑定关系设置,并对比了两种方式的优缺点。

此外,详细说明了消息转换器的作用,推荐使用Jackson JSON转换器替代默认实现,以实现跨语言兼容和更好的可读性。文章还提供了核心参数的避坑指南,帮助开发者正确处理组件生命周期问题。最后展示了如何通过RabbitTemplate优雅地发送和接收自定义对象消息。

Java客户端组件声明:

在 Java 客户端中,声明队列和交换机主要有两种主流方式:

一种是使用 **RabbitMQ 原生 Java 客户端(AMQP Client)**另一种是使用 Spring Boot(Spring AMQP)

在实际企业开发中,Spring Boot 方式最为常用,因为它通过注解或配置类将声明过程高度抽象化了。

方式一:使用 Spring Boot (Spring AMQP)

Spring AMQP 提供了两种声明方式:配置类(Bean 注入)注解驱动

1.1 通过配置类(@Configuration)声明

这种方式结构清晰,适合在项目启动时统一管理队列和交换机。

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

@Configuration
public class RabbitConfig {

    // 1. 声明交换机 (以 Topic 类型为例)
    @Bean
    public TopicExchange topicExchange() {
        // 参数:交换机名称、是否持久化、是否自动删除
        return new TopicExchange("boot.topic.exchange", true, false);
    }

    // 2. 声明队列
    @Bean
    public Queue myQueue() {
        // 参数:队列名称、是否持久化、是否排他、是否自动删除
        return new Queue("boot.topic.queue", true, false, false);
    }

    // 3. 声明绑定关系 (将队列绑定到交换机,并指定 Routing Key)
    @Bean
    public Binding bindingQueueToExchange(Queue myQueue, TopicExchange topicExchange) {
        return BindingBuilder
                .bind(myQueue)
                .to(topicExchange)
                .with("boot.#"); // 路由键匹配规则
    }
}
1.2 通过注解(@RabbitListener)动态声明

如果我们不想写一堆配置类,可以在消费者监听消息时,直接在注解中动态创建。

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

@Component
public class RabbitConsumer {

    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "boot.anno.queue", durable = "true"),
        exchange = @Exchange(name = "boot.anno.exchange", type = "topic"),
        key = "anno.#"
    ))
    public void listen(String message) {
        System.out.println("接收到消息: " + message);
    }
}

方式二:使用 RabbitMQ 原生 Java 客户端

如果不使用 Spring 框架,纯 Java 项目需要通过 Channel 对象的 API 来显式声明。

复制代码
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class RabbitNativeDeclare {
    public static void main(String[] args) throws Exception {
        // 1. 创建连接工厂并建立连接
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setUsername("guest");
        factory.setPassword("guest");
        
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            
            String exchangeName = "native.direct.exchange";
            String queueName = "native.direct.queue";
            String routingKey = "native.key";

            // 2. 声明交换机 
            // 参数: exchange, type, durable, autoDelete, arguments
            channel.exchangeDeclare(exchangeName, "direct", true, false, null);

            // 3. 声明队列
            // 参数: queue, durable, exclusive, autoDelete, arguments
            channel.queueDeclare(queueName, true, false, false, null);

            // 4. 绑定队列和交换机
            // 参数: queue, exchange, routingKey
            channel.queueBind(queueName, exchangeName, routingKey);
            
            System.out.println("原生客户端:交换机、队列及绑定关系创建成功!");
        }
    }
}

核心参数避坑指南

无论使用哪种方式,声明时的几个布尔值(Boolean)参数至关重要,决定了组件的生命周期:

  • Durable (持久化)

    • true(推荐):RabbitMQ 服务重启后,交换机/队列依然存在。

    • false:重启后消失。

  • AutoDelete (自动删除)

    • true:当没有消费者连接这个队列(或没有队列绑定这个交换机)时,自动将其销毁。

    • false(推荐):即使没人用也一直保留。

  • Exclusive (排他队列)

    • true:只对首次声明它的连接(Connection)可见,且连接断开时队列会自动删除。通常用于临时、单消费者场景。

注意: 如果 RabbitMQ 中已经存在 同名的队列或交换机,但代码中声明的参数(如 durable 状态)与服务端现有的不一致 ,程序会直接报错(406 PRECONDITION_FAILED)。修改参数时,需要先去 RabbitMQ 管控台手动删除旧的组件



消息转换器:

在 RabbitMQ 的 Java 客户端中,消息转换器(MessageConverter) 是一个非常重要的组件。

简单来说:网络传输只能识别字节流(byte[] ,而我们在 Java 代码里使用的是各种对象(如 StringUserMap 等)。

  • 发送消息时 :需要把 Java 对象变成 byte[](序列化)。

  • 接收消息时 :需要把 byte[] 变回 Java 对象(反序列化)。

如果不使用消息转换器,你每次发消息都得手动写代码去转字节数组,非常痛苦。

1. 原生 Java 客户端 vs Spring AMQP

  • 原生 Java 客户端(com.rabbitmq.client)没有 内置的高级消息转换器。你必须手动将对象转成 JSON 字符串,再转成 byte[] 才能发送。

  • Spring AMQP(Spring Boot)自带MessageConverter 接口,并且提供了完美的自动化支持。只要配置好,你直接扔一个 Java 对象给它,它会自动帮你转。

因此,我们重点讲解 Spring Boot 中的消息转换器。


2. 默认的转换器

如果你在 Spring Boot 中不做任何配置,默认使用的是 SimpleMessageConverter

  • 它是怎么工作的 :它使用的是 Java 原生的序列化机制(ObjectOutputStream)。

  • 缺点

    1. 可读性极差 :在 RabbitMQ 管控台看消息,全是 \xAC\xED\x00\x05sr\x00... 这样的二进制乱码。

    2. 体积大:占用更多的网络带宽和磁盘空间。

    3. 安全性与跨语言问题:其他语言(如 Python、Go)很难解析 Java 原生序列化的数据。


3. 最佳实践:配置 JSON 消息转换器

在企业开发中,几乎 100% 都会将其替换为 Jackson JSON 转换器

第一步:在配置类中注入 Bean

你只需要在项目中任何一个 @Configuration 类里,向 Spring 容器注入 Jackson2JsonMessageConverter即可。Spring AMQP 会自动识别并切换。

复制代码
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 RabbitConfig {

    // 只要配置了这个 Bean,发送和接收都会自动使用 JSON
    @Bean
    public MessageConverter jsonMessageConverter() {
        return new Jackson2JsonMessageConverter();
    }
}
第二步:发送对象

配置好后,使用 RabbitTemplate 发送消息时,你可以直接传一个自定义的对象(无需实现 Serializable 接口)

复制代码
@Autowired
private RabbitTemplate rabbitTemplate;

public void sendUserMessage() {
    User user = new User("张三", 18);
    // 自动转换为 JSON 字符串 {"name":"张三","age":18} 的字节流发送
    rabbitTemplate.convertAndSend("amqp.exchange", "user.key", user);
}
第三步:优雅地接收对象

在消费者端,直接用对应的实体类接收,Spring 会自动把 JSON 解析回 Java 对象:

复制代码
@RabbitListener(queues = "user.queue")
public void listenUser(User user) { // 👈 直接接收对象
    System.out.println("收到用户消息:" + user.getName() + ", 年龄:" + user.getAge());
}

4. 它是怎么知道应该转成什么对象的

我们可能会好奇:RabbitMQ 队列里存的明明只是纯文本的 JSON 字符串,消费者怎么知道要把它反序列化成 User 类,而不是 Order

这就涉及到 AMQP 消息的核心结构了。一条消息由两部分组成:

  1. Body(消息体):即 JSON 字符串。

  2. Properties(消息头/属性)关键就在这里

当使用 Jackson2JsonMessageConverter 发送消息时,它会在消息头中自动添加一个属性:

  • __TypeId__ : com.example.domain.User(对象的全类名)

当消费者收到消息时,转换器会去读这个 __TypeId__,从而知道:我要把它转成 User 对象,然后帮你在后台默默完成转换。


结语:

如果对你有帮助,请点赞,关注,收藏,你的支持就是我最大的鼓励!