目录
[1.1 传统代码示例](#1.1 传统代码示例)
[1.2 传统方式的痛点](#1.2 传统方式的痛点)
[2.1 统一配置代码解析](#2.1 统一配置代码解析)
[2.2 统一配置的优势](#2.2 统一配置的优势)
[3.1 配置RabbitTemplate(发送端)](#3.1 配置RabbitTemplate(发送端))
[3.2 配置监听器工厂(接收端)](#3.2 配置监听器工厂(接收端))
[4.1 发送端:两种方式示例](#4.1 发送端:两种方式示例)
[4.2 接收端:两种方式处理](#4.2 接收端:两种方式处理)
前言
在微服务架构中,RabbitMQ作为一款成熟的消息中间件,其应用非常广泛。然而,随着业务的发展,我们往往需要管理数十个乃至上百个队列和交换机。如果按照传统的做法,为每一个队列都编写一套独立的 @Bean 声明,配置类将会变得异常臃肿且难以维护。
本文将基于SpringBoot,介绍一种统一配置 RabbitMQ队列、交换机和绑定的优雅方式。通过对比传统配置方式,你将深刻体会到统一配置在简化代码、提升可维护性方面的巨大优势。同时,文章还将结合生产实践,展示如何使用Protostuff进行高性能序列化,以及如何配置发送端和接收端。
关于Protostuff序列化工具类的详细实现和原理,可以参考我的另一篇文章:
Protostuff 序列化工具类详解:高性能Java对象序列化方案
一、传统配置方式:繁琐且重复
在早期的SpringBoot RabbitMQ配置中,最常见的做法是为每一个业务场景单独声明其所需的交换机、队列和绑定关系。
1.1 传统代码示例
java
@Configuration
public class TraditionalRabbitMQConfig {
// 业务A:直连交换机
@Bean
public DirectExchange directExchangeForBizA() {
return new DirectExchange("biz.a.exchange", true, false);
}
@Bean
public Queue queueForBizA() {
return new Queue("biz.a.queue", true);
}
@Bean
public Binding bindingForBizA() {
return BindingBuilder.bind(queueForBizA())
.to(directExchangeForBizA())
.with("biz.a.routing.key");
}
// 业务B:扇出交换机
@Bean
public FanoutExchange fanoutExchangeForBizB() {
return new FanoutExchange("biz.b.exchange", true, false);
}
@Bean
public Queue queueForBizB() {
return new Queue("biz.b.queue", true);
}
@Bean
public Binding bindingForBizB() {
return BindingBuilder.bind(queueForBizB())
.to(fanoutExchangeForBizB());
}
// ... 随着业务增加,这里会越来越长
}
1.2 传统方式的痛点
-
代码臃肿 :每增加一个业务,就需要增加至少3个
@Bean方法。 -
配置分散:交换机、队列、路由键的名称通常硬编码在注解或字符串中,不易统一管理。
-
维护困难:当需要修改一个队列的名称或属性时,需要在整个配置类中搜索并修改,容易出错。
二、统一配置方式:优雅且高效
统一配置的核心思想是利用Spring AMQP提供的 **Declarables** 类,将所有需要声明的对象(交换机、队列、绑定)集中在一个 @Bean 方法中创建并返回。
2.1 统一配置代码解析
以下是您提供的配置类代码,它完美地展示了如何实现统一管理。
java
import com.cetcnav.rabbitMQListener.MyRabbitMQConfigProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Configuration
public class RabbitMQConfig {
@Autowired
private MyRabbitMQConfigProperties rabbitMQProperties; // 配置属性类,集中管理名称
/**
* 统一声明所有的交换机、队列和绑定关系。
* 这是统一配置的核心所在。
*/
@Bean
public Declarables unifiedDeclarations() {
List<Declarable> declarables = new ArrayList<>();
// --- 业务1: tpos-位置业务 (Direct Exchange) ---
DirectExchange exchangeDirectTpos = new DirectExchange(
rabbitMQProperties.getExchange().getDirect().getTPos(), true, false);
Queue queueTpos = new Queue(rabbitMQProperties.getQueue().getTPos(), true, false, false);
Binding tposBinding = BindingBuilder.bind(queueTpos)
.to(exchangeDirectTpos)
.with(rabbitMQProperties.getRouting().getTPos());
declarables.add(exchangeDirectTpos);
declarables.add(queueTpos);
declarables.add(tposBinding);
// --- 业务2: tlogin-登录登出业务 (Direct Exchange) ---
DirectExchange exchangeDirectTlogin = new DirectExchange(
rabbitMQProperties.getExchange().getDirect().getTLogin(), true, false);
Queue queueTlogin = new Queue(rabbitMQProperties.getQueue().getTLogin(), true, false, false);
Binding tloginBinding = BindingBuilder.bind(queueTlogin)
.to(exchangeDirectTlogin)
.with(rabbitMQProperties.getRouting().getTLogin());
declarables.add(exchangeDirectTlogin);
declarables.add(queueTlogin);
declarables.add(tloginBinding);
// --- 业务3: pset-指令下发业务 (Fanout Exchange) ---
FanoutExchange exchangeFanoutPset = new FanoutExchange(
rabbitMQProperties.getExchange().getFanout().getPSet(), true, false);
Queue queuePset = new Queue(rabbitMQProperties.getQueue().getPSet(), true, false, false);
Binding psetBinding = BindingBuilder.bind(queuePset).to(exchangeFanoutPset);
declarables.add(exchangeFanoutPset);
declarables.add(queuePset);
declarables.add(psetBinding);
log.info("统一声明了 {} 个 RabbitMQ 组件 (交换机、队列、绑定)", declarables.size());
return new Declarables(declarables);
}
// ... (RabbitTemplate 和 监听器工厂的配置,下文会详细说明)
}
2.2 统一配置的优势
-
结构清晰:所有声明集中在一个方法内,业务逻辑一目了然。
-
易于维护 :配合
MyRabbitMQConfigProperties配置类,所有名称统一管理。修改时只需改动配置文件,无需改动Java代码。 -
减少样板代码 :告别了大量的独立
@Bean方法,使配置类更加简洁。
三、配置类深度解析:发送与接收
一个完整的RabbitMQ配置,除了声明队列交换机,还需要配置发送端(RabbitTemplate)和接收端(监听器工厂)。
3.1 配置RabbitTemplate(发送端)
配置 RabbitTemplate 主要是为了设置消息转换器和添加发送回调,以便监控消息的送达情况。
java
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// 设置消息转换器,默认将对象转为JSON
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
// 确认消息是否成功发送到Exchange
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
log.info("消息[{}]发送到交换机成功", correlationData != null ? correlationData.getId() : "null");
} else {
log.error("消息发送到交换机失败: {}", cause);
}
});
// 确认消息从Exchange路由到Queue是否失败
rabbitTemplate.setReturnsCallback(returned -> {
log.error("消息无法路由到队列: 消息体: {}, 交换机: {}, 路由键: {}, 返回码: {}, 信息: {}",
returned.getMessage(), returned.getExchange(), returned.getRoutingKey(),
returned.getReplyCode(), returned.getReplyText());
});
return rabbitTemplate;
}
3.2 配置监听器工厂(接收端)
监听器工厂控制着消费者的行为,例如消息预取数量、确认模式等。
java
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// 设置消息转换器,保证接收到的消息可以自动转换为对象(如果发送的是JSON)
factory.setMessageConverter(new Jackson2JsonMessageConverter());
// 关键配置:prefetch=1,确保每次只消费一条消息,处理完后再取下一条,实现公平分发
factory.setPrefetchCount(1);
// 设置手动确认模式,由业务代码控制消息的ack
// factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
return factory;
}
四、发送端与接收端实战:支持多种消息格式
在实际项目中,我们可能需要发送不同格式的消息,例如纯文本JSON字符串,或者经过Protostuff压缩的二进制数据。
4.1 发送端:两种方式示例
java
// 方式一:发送序列化后的二进制数据 (Protostuff)
public void sendWithProtostuff(Object form) {
byte[] data = ProtostuffSerializer.serialize(form);
MessageProperties properties = new MessageProperties();
properties.setContentType("application/x-protostuff"); // 自定义类型,便于接收方识别
properties.setContentLength(data.length);
Message message = new Message(data, properties);
// 对于Fanout交换机,路由键填空字符串
rabbitTemplate.convertAndSend(rabbitMQProperties.getExchange().getFanout().getPSet(), "", message);
log.info("已发送Protostuff序列化消息至 {}", rabbitMQProperties.getExchange().getFanout().getPSet());
}
// 方式二:发送JSON字符串 (适用于Direct/直连交换机)
public void sendWithJson(Object toMqtt) {
String jsonMessage = JSONObject.toJSONString(toMqtt);
rabbitTemplate.convertAndSend(
rabbitMQProperties.getExchange().getDirect().getTPos(),
rabbitMQProperties.getRouting().getTPos(),
jsonMessage
);
log.info("已发送JSON消息至 {}", rabbitMQProperties.getExchange().getDirect().getTPos());
}
4.2 接收端:两种方式处理
在消费者端,通过 @RabbitListener 接收消息。根据消息的内容类型,可以采用不同的处理方式。
java
import org.springframework.amqp.core.Message;
import com.alibaba.fastjson.JSONObject;
@Component
@Slf4j
public class MessageConsumer {
/**
* 接收并反序列化Protostuff格式的消息
* 监听的是Fanout交换机绑定的队列
*/
@RabbitListener(queues = "${my-rabbitmq-config.queue.p-set}")
public void pSetListener(Message message) {
try {
byte[] body = message.getBody();
// 调用Protostuff工具类反序列化
PsetDataVO psetDataVO = ProtostuffSerializer.deserialize(body, PsetDataVO.class);
log.info("接收到pset信息:【{}】", JSONObject.toJSONString(psetDataVO));
// ... 处理业务逻辑
} catch (Exception e) {
log.error("处理pset消息失败", e);
}
}
/**
* 接收JSON字符串格式的消息
* 监听的是Direct交换机绑定的队列
*/
@RabbitHandler
@RabbitListener(queues = "${my-rabbitmq-config.queue.t-pos}")
public void tposListener(Message message) {
// 直接获取消息体并转为字符串,因为发送的是JSON字符串
String msg = new String(message.getBody());
log.info("接收到tpos消息:{}", msg);
// 如果需要转为对象,可以使用 JSONObject.parseObject(msg, TargetClass.class)
// ... 处理业务逻辑
}
}
五、总结
本文通过对比,展示了SpringBoot中RabbitMQ的两种配置方式。统一配置结合外部化配置属性,极大地简化了多队列、多交换机的管理,是构建大型、可维护系统的推荐实践。
同时,通过结合高性能的Protostuff序列化工具,我们可以在消息中间件传输中进一步压缩数据体积,提升系统吞吐量。希望本文对你优化RabbitMQ的使用有所帮助。