【RabbitMQ #7】 | 消息转换器

简介:学习 RabbitMQ 消息转换器,对比 SpringAMQP 默认 JDK 序列化问题,使用 Go 语言实现等效消息转换器,实现 Java 与 Go 跨语言收发消息。

一、案例需求

测试利用 SpringAMQP 发送对象类型的消息

  1. 声明一个队列,名为 object.queue
  2. 编写单元测试,向队列中直接发送一条消息,消息类型为 Map
  3. 在 RabbitMQ 管理控制台查看消息,总结存在的问题

核心知识点:消息转换器 MessageConverter SpringAMQP 中MessageConverter负责两件事:

  1. 发送消息:Java 对象 → 字节数组(序列化)
  2. 消费消息:字节数组 → Java 对象(反序列化)

二、踩坑:SpringAMQP 默认序列化问题

SpringAMQP不手动配置 Jackson 转换器 时,默认使用SimpleMessageConverter,底层是JDK 原生序列化。

  • 现象:发送 Map 对象后,MQ 控制台看到一串不可读的二进制长串(本例 129 字节)
  • 缺点:
    1. 可读性极差,MQ 网页控制台无法直接阅读消息内容
    2. JDK 序列化是 Java 专属协议,跨语言无法解析(Go/Python 不能直接解码)
    3. 存在反序列化安全漏洞风险

解决方案:替换为Jackson2JsonMessageConverter,使用 JSON 序列化,实现跨语言通信。

java 复制代码
// Spring配置类,注入JSON消息转换器
@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}

三、Go 语言实现等效消息转换器

Go 语言的amqp091-go库没有内置消息转换器 ,需要我们手动封装序列化 / 反序列化逻辑,对标 Spring 的 MessageConverter。 原理:发送时json.Marshal对象转字节;消费时json.Unmarshal字节转回对象。

1. 安装依赖

Go 复制代码
go get github.com/rabbitmq/amqp091-go

2. 生产者 send.go(声明队列 + 发送 Map 消息)

Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"github.com/rabbitmq/amqp091-go"
)

// 工具函数:错误捕获
func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}

func main() {
	// 1. 连接RabbitMQ服务
	conn, err := amqp091.Dial("amqp://guest:guest@localhost:5672/")
	failOnError(err, "连接MQ失败")
	defer conn.Close()

	// 2. 创建通道
	ch, err := conn.Channel()
	failOnError(err, "打开channel失败")
	defer ch.Close()

	// 3. 声明队列 object.queue(和Java案例保持一致)
	q, err := ch.QueueDeclare(
		"object.queue", // 队列名称
		true,           // durable:持久化队列
		false,          // delete when unused:队列不再使用时不删除
		false,          // exclusive:非独占队列
		false,          // no-wait:等待服务端响应
		nil,            // arguments:额外参数
	)
	failOnError(err, "声明队列失败")

	// 4. 构造消息,等价Java Map<String,Object>
	msg := map[string]any{
		"name": "Jack",
		"age":  21,
	}

	// 【消息转换器逻辑】JSON序列化,等同于Spring Jackson消息转换器
	body, err := json.Marshal(msg)
	failOnError(err, "json序列化失败")

	// 5. 发送消息
	err = ch.Publish(
		"",         // exchange:空,使用默认交换机,直接路由到队列
		q.Name,     // routingKey:队列名
		false,      // mandatory
		false,      // immediate
		amqp091.Publishing{
			ContentType: "application/json", // 标记消息类型为JSON,给消费者识别
			Body:        body,
		})
	failOnError(err, "发送消息失败")

	fmt.Printf("消息发送成功: %s\n", body)
}

3. 消费者 receive.go(消费 object.queue,解析 Map 消息)

Go 复制代码
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"github.com/rabbitmq/amqp091-go"
)

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}

func main() {
	// 建立连接
	conn, err := amqp091.Dial("amqp://guest:guest@localhost:5672/")
	failOnError(err, "连接MQ失败")
	defer conn.Close()

	ch, err := conn.Channel()
	failOnError(err, "打开channel失败")
	defer ch.Close()

	// 声明队列(生产者消费者都声明,保证队列一定存在)
	q, err := ch.QueueDeclare(
		"object.queue",
		true,
		false,
		false,
		false,
		nil,
	)
	failOnError(err, "声明队列失败")

	// 订阅队列消息
	msgs, err := ch.Consume(
		q.Name,
		"",
		true,  // autoAck:自动确认消息
		false,
		false,
		false,
		nil,
	)
	failOnError(err, "注册消费者失败")

	forever := make(chan bool)

	// 协程持续监听消息
	go func() {
		for d := range msgs {
			// 【消息转换器逻辑】JSON反序列化,字节数组转回map
			var data map[string]any
			err := json.Unmarshal(d.Body, &data)
			if err != nil {
				log.Println("解析消息失败:", err)
				continue
			}
			fmt.Printf("收到消息: name=%v, age=%v\n", data["name"], data["age"])
		}
	}()

	fmt.Println("等待消息...")
	<-forever
}

四、封装通用 MessageConverter(进阶)

对标 Spring,封装通用转换器结构体,统一序列化 / 反序列化方法,方便复用:

Go 复制代码
// MessageConverter 消息转换器,对标SpringAMQP MessageConverter
type MessageConverter struct{}

func NewMessageConverter() *MessageConverter {
	return &MessageConverter{}
}

// ToBytes 对象转字节数组(发送时序列化)
func (c *MessageConverter) ToBytes(obj any) ([]byte, error) {
	return json.Marshal(obj)
}

// FromBytes 字节数组转回对象(消费时反序列化)
func (c *MessageConverter) FromBytes(data []byte, target any) error {
	return json.Unmarshal(data, target)
}

使用示例:

Go 复制代码
converter := NewMessageConverter()
body, err := converter.ToBytes(msg)
// 消费时
var data map[string]any
err := converter.FromBytes(d.Body, &data)

五、核心总结

  1. SpringAMQP 默认使用 JDK 序列化,Java 专属二进制格式,Go 无法解析,MQ 控制台是乱码二进制。
  2. 推荐统一使用JSON 序列化,可读性好,天然支持跨语言(Java ↔ Go)。
  3. SpringAMQP 通过MessageConverter完成对象与字节的转换;Go 没有内置实现,需要手动封装 json 序列化逻辑。
  4. 最佳实践:消息头带上ContentType:application/json,让消费者识别消息编码格式。

六、运行测试步骤

  1. 启动 RabbitMQ 服务
  2. 终端 1 运行消费者:go run receive.go,等待接收消息
  3. 终端 2 运行生产者:go run send.go,发送消息
  4. 查看控制台输出,同时访问 RabbitMQ 管理页面,查看object.queue消息(JSON 可读)
相关推荐
需要8262 小时前
分布式事务:Seata AT/TCC/SAGA 的取舍与踩坑
java·spring boot·分布式·spring·spring cloud
CS创新实验室5 小时前
为什么三台机器就能选出一个“带头大哥“?——Raft 一致性算法,一次讲透
分布式·操作系统
本人手速666+6 小时前
企微开发API如何设计客户冻结状态?WeComApi 在删除、投诉和异常客户场景中的自动化边界
运维·分布式·自动化·企业微信·企微外部群开发·wecomapi·企业微信二次开发
灯澜忆梦8 小时前
【RabbitMQ #4】 | Work 任务模型
分布式·rabbitmq
程序猿乐锅8 小时前
【黑马点评 | 第七篇】Redis 分布式锁的两种实现
java·数据库·redis·分布式·spring·缓存
千里码aicood9 小时前
基于Hadoop的机票价格波动分析系统的设计与实现
大数据·hadoop·分布式
Thomas.Sir7 天前
第21课:PyTorch|GPU多卡训练与分布式训练基础【让多卡并行成为你的加速引擎】
人工智能·pytorch·分布式
Cicada1287 天前
库存消息消费的正确性设计——从幂等窗口到批量流水线
分布式·系统架构