RabbitMQ实现消息发送接收——实战篇(路由模式)

本篇博文将带领大家一起学习rabbitMQ如何进行消息发送接收,我也是在写项目的时候边学边写,有不足的地方希望在评论区留下你的建议,我们一起讨论学习呀~

需求背景

先说一下我的项目需求背景,社区之间可以进行物资借用,当有社区提交物资借用申请时,需要通过RabbitMQ将这条消息发送到被借用物资的社区,同时在界面进行提示。

先把依赖引入一下

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>

application.yml做好配置:

spring:rabbitmq:host: localhostport: 5672username: guestpassword: guest

工具类实现

先选择以何种方式进行消息发送,这里根据需求我选择使用RabbitMQ的路由模式进行消息发送,先来配置一下相应工具类:

先配置RabbitMQ的配置类

/*** @Title: RabbitMQConfig* @Author yinan* @Package com.yinan.config.RabbitConfig* @Date 2024/12/13 13:58* @description: RabbitMQ配置类*/
@Configuration
public class RabbitMQConfig {@Beanpublic RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {return new RabbitAdmin(connectionFactory);}//    声明一个交换机@Beanpublic DirectExchange borrowMaterialExchange(){return new DirectExchange("borrow_material_exchange");}//    动态绑定队列时使用的方法(具体绑定逻辑在下面的监听器中实现)
//    @Bean
//    public Queue communityQueue(){
//        return new Queue("communityQueue");
//    }@Beanpublic Jackson2JsonMessageConverter messageConverter() {return new Jackson2JsonMessageConverter();}@Beanpublic AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory) {RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);rabbitTemplate.setMessageConverter(messageConverter());return rabbitTemplate;}
}

为了确保消息发送和接收时都以 JSON 格式处理,可以在 Spring 配置中添加 Jackson2JsonMessageConverter。这样,发送端会将 MaterialBorrowing 对象序列化为 JSON,接收端会自动将 JSON 反序列化回 MaterialBorrowing 对象。 

绑定交换机和对应队列

/*** @Title: RabbitMQBindRoutingConfig* @Author yinan* @Package com.yinan.config.RabbitConfig* @Date 2024/12/13 14:21* @description:  动态绑定路由配置*/
@Component
@Slf4j
public class RabbitMQBindRoutingConfig {@Autowiredprivate DirectExchange borrowMaterialExchange;@Autowiredprivate RabbitAdmin rabbitAdmin;/*** 以社区ID为路由键,为指定社区动态创建队列并绑定到交换机* @param communityId 社区ID*/public void bindRouting(String communityId){
//        创建队列Queue queue = new Queue("queue_" + communityId);
//        动态绑定交换机和指定队列Binding binding = BindingBuilder.bind(queue).to(borrowMaterialExchange).with(communityId);rabbitAdmin.declareExchange(borrowMaterialExchange);rabbitAdmin.declareBinding(binding);log.info("队列绑定成功,社区ID----》" + communityId + ",队列名称----》" + queue.getName() + ",交换机名称----》" + borrowMaterialExchange.getName());}}

动态声明队列

@Configuration
@Slf4j
public class QueueDeclareConfig {@Autowiredprivate RabbitAdmin rabbitAdmin;public void dynamicDeclareQueue(String communityId){String queueName = String.format("queue_%s",communityId);Queue queue = new Queue(queueName,true);rabbitAdmin.declareQueue(queue);log.info("队列声明成功");}
}

在创建声明队列的时候,我们希望的是根据我们的规则在调用接口的时候去创建指定名称的队列,所以可以使用动态声明对列而不是直接在平台上进行配置。

消息发送

@Component
@Slf4j
public class MessageSendConfig {@Autowiredprivate AmqpTemplate amqpTemplate;public void sendMessage(Object message,String communityId){System.out.println("发送消息:" + message);amqpTemplate.convertAndSend("borrow_material_exchange",communityId, message);log.info("发送消息成功------->"+message);}}

消息接收(动态声明与监听结合),这里你可以先思考一下为什么要用这种方式实现消息接收,而不是使用@RabbitListener去动态获取某个队列接收消息。

/*** @Title: MessageRecieveConfig* @Author yinan* @Package com.yinan.config.RabbitConfig* @Date 2024/12/13 12:53* @description: 动态监听接收消息*/
@Service
@Slf4j
public class MessageRecieveConfig<T> {private final ConnectionFactory connectionFactory;public MessageRecieveConfig(ConnectionFactory connection) {this.connectionFactory = connection;}public void recieveMessage(String communityId,Class<T> objectType){String queueName = String.format("queue_%s",communityId);
//        创建监听容器SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);container.setQueueNames(queueName);
//        处理消息消费逻辑container.setMessageListener(message -> {try {// 将字节数组转换为字符串String messageBody = new String(message.getBody(), StandardCharsets.UTF_8);System.out.println("接收到的消息:" + messageBody);// 如果需要将消息解析为对象(例如 MaterialBorrowing)ObjectMapper objectMapper = new ObjectMapper();T result = objectMapper.readValue(messageBody, objectType);System.out.println("反序列化后的消息:" + result);} catch (Exception e) {log.error("处理消息时发生错误:", e);}});// 确保自动确认container.setAcknowledgeMode(AcknowledgeMode.AUTO);container.start();log.info("动态监听已启动,监听队列------->"+queueName);}}

在你的项目中分别调用就行了,需要注意的是你必须确保在消息发送的时候你的队列已经创建完成且和对应交换机进行了绑定,不然可能会导致消息发送失败。

ok,我们启动项目

你会发现你的项目根本启动不起来,原因是因为对于 Spring AMQP 的监听器来说,必须确保监听的队列已经存在于 RabbitMQ 中,否则会抛出类似 DeclarationException 的错误。

所以我们考虑可以通过动态声明队列,在程序运行时确保 RabbitMQ 上创建好所需的队列。

动态声明队列的含义

动态声明队列是指程序在运行时,通过代码检查或创建 RabbitMQ 中尚不存在的队列,而不是手动预先配置好所有队列。这种方式可以自动帮你在 RabbitMQ 中创建所需的队列,而无需手动操作。

这里说一下为什么需要动态绑定队列而不直接使用@RabbitListener?

为什么使用 SimpleMessageListenerContainer 动态绑定队列

  • SimpleMessageListenerContainer 不需要在项目启动时绑定队列。你可以在用户调用接口时动态创建队列,并动态监听它。
  • 特点
    • 队列在用户调用接口时才会被动态创建(通过 RabbitAdmin 或其他机制)。
    • 动态创建队列和监听时,项目启动时不会尝试绑定不存在的队列,因此不会报错。
  • 适用场景:非常适合动态队列需求,比如队列名依赖用户输入或业务逻辑,且不想在项目启动时绑定固定的队列。

使用 @RabbitListener 的情况

@RabbitListener 会在项目启动时绑定到指定的队列。

  • 要求:如果绑定的队列在 RabbitMQ 中不存在,项目启动时就会抛出异常,类似 DeclarationException,这也就是上面为什么会报错的原因。
  • 解决办法
    • 提前创建队列:在 RabbitMQ 中手动创建队列,或通过 RabbitAdmin 在项目启动时自动创建队列。
    • 动态队列名:如果队列名是动态的,可以结合 SpEL 表达式,但队列仍然需要在项目启动时确保存在。
SpEL 表达式

如果你的需求中已经确定队列已经创建好的,但是需要动态去获取队列,可以使用如下形式:

@RabbitListener(queues = "#{T(java.lang.String).format('queue_%s', 'borrowedCommunityId')}")

这个表达式 是 Spring AMQP 中用于动态指定队列名称的 SpEL 表达式(Spring Expression Language),它的作用就是会动态生成一个队列名称,基于你传入的参数构造队列名。

详解
1. 关键部分解析
  • T(java.lang.String)

    • T 是 SpEL 用于引用 Java 类 的方式。
    • java.lang.String 是目标 Java 类,表明你可以调用 String 类的静态方法。
  • .format()

    • String.format() 是 Java 中的静态方法,用于格式化字符串。
    • 格式化字符串的格式是 'queue_%s'%s 是占位符,用于拼接动态内容。
  • 'queue_%s'

    • 这是格式化字符串的模板。%s 表示字符串占位符。
  • 动态参数(例如 borrowedCommunityId

    • 它会替换 %s,生成队列名。例如,当 borrowedCommunityId 的值是 123 时,结果是:queue_123
2. 具体实例

假设 borrowedCommunityId = "123"

String result = String.format("queue_%s", "123");
System.out.println(result); // 输出:queue_123

在 SpEL 中,这等同于:

queues = "#{T(java.lang.String).format('queue_%s', '123')}"

这会动态生成队列名称为 queue_123


为什么用 SpEL?

Spring AMQP 的 @RabbitListener 注解中,queues 参数支持 SpEL 表达式。这使得我们可以动态决定要监听的队列,而不是写死某个固定的队列名称。


实际应用场景

就比如在我的代码中,可能有多个社区队列,例如:

  • queue_123(社区 ID 为 123 的队列)
  • queue_456(社区 ID 为 456 的队列)

使用 queues = "#{T(java.lang.String).format('queue_%s', borrowedCommunityId)}",可以动态生成不同社区的队列名称,从而实现按社区路由的功能。

启动项目之后,调用接口就可以发送消息了

但是你会发现消息消费的逻辑并没有在控制台中打印出来,这个时候你就要考虑是不是以下几个问题了:

交换机和队列是否已经绑定成功(可以在平台上进行查看)

是否绑定到了对应的交换机:amqpTemplate.convertAndSend("borrow_material_exchange",communityId, message);红色部分指定交换机名称,如果不指定,那么就会使用默认的交换机,所以肯定也是接收不到值的。

当然,还有其他可能,如果你的项目中遇到了,可以在评论区留言,我们一起学习~

最后,重新修改代码调用接口,就可以接收到消息了

对于在界面进行消息提示的功能,这里先不写出来了,我会在后面的博客中进行更新~

【都看到这了,点赞加关注,收藏不迷路呀~】😚😚

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/490060.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python的3D可视化库【vedo】2-1 (plotter模块) 绘制器的使用

文章目录 1 相关用语及其关系2 Plotter类的基本使用3 Plotter类具体的初始化设置3.1 全部初始化参数3.2 使用不同的axes vedo是Python实现的一个用于辅助科学研究的3D可视化库。 vedo的plotter模块封装了绘制器类Plotter。 Plotter实例可以用于显示3D图形对象、控制渲染器行为、…

【开源大屏】玩转开源积木BI,从0到1设计一个大屏

积木 BI 重磅推出免费大屏设计器&#xff01;功能超强大&#xff0c;操作超流畅&#xff0c;体验超酷炫。快来体验一下吧。 让我们一起来看一下如何从0到1设计一个大屏。 一、积木BI大屏介绍 积木BI可视化数据大屏 是一站式数据可视化展示平台&#xff0c;旨在帮助用户快速通…

微信小程序--创建一个日历组件

微信小程序–创建一个日历组件 可以创建一个日历组件&#xff0c;来展示当前月份的日期&#xff0c;并支持切换月份的功能。 一、目录结构 /pages/calendarcalendar.wxmlcalendar.scsscalendar.jscalendar.json二、calendar.wxml <view class"calendar"><…

【Python网络爬虫笔记】11- Xpath精准定位元素

目录 一、Xpath 在 Python 网络爬虫中的作用&#xff08;一&#xff09;精准定位元素&#xff08;二&#xff09;应对动态网页&#xff08;三&#xff09;数据结构化提取 二、Xpath 的常用方法&#xff08;一&#xff09;节点选取&#xff08;二&#xff09;谓词筛选&#xff0…

现代密码学总结(上篇)

现代密码学总结 &#xff08;v.1.0.0版本&#xff09;之后会更新内容 基本说明&#xff1a; ∙ \bullet ∙如果 A A A是随机算法&#xff0c; y ← A ( x ) y\leftarrow A(x) y←A(x)表示输入为 x x x ,通过均匀选择 的随机带运行 A A A,并且将输出赋给 y y y。 ∙ \bullet …

深度学习训练参数之学习率介绍

学习率 1. 什么是学习率 学习率是训练神经网络的重要超参数之一&#xff0c;它代表在每一次迭代中梯度向损失函数最优解移动的步长&#xff0c;通常用 η \eta η 表示。它的大小决定网络学习速度的快慢。在网络训练过程中&#xff0c;模型通过样本数据给出预测值&#xff0…

lc46全排列——回溯

46. 全排列 - 力扣&#xff08;LeetCode&#xff09; 法1&#xff1a;暴力枚举 总共n!种全排列&#xff0c;一一列举出来放入list就行&#xff0c;关键是怎么去枚举呢&#xff1f;那就每次随机取一个&#xff0c;然后删去这个&#xff0c;再从剩下的数组中继续去随机选一个&a…

Docker 安装 Seata2.0.0 (快速配置)

说明&#xff1a;已安装Docker、MySql等&#xff0c;案例使用Mysql数据库模式、Nacos配置信息 1、准备工作 1.1 拉取镜像 [rootTseng ~]# docker pull seataio/seata-server:2.0.0 2.0.0: Pulling from seataio/seata-server 001c52e26ad5: Already exists d9d4b9b6e964: P…

渗透测试-前端验签绕过之SHA256+RSA

本文是高级前端加解密与验签实战的第2篇文章&#xff0c;本系列文章实验靶场为Yakit里自带的Vulinbox靶场&#xff0c;本文讲述的是绕过SHA256RSA签名来爆破登录。 绕过 根据提示可以看出这次签名用了SHA2556和RSA两个技术进行加密。 查看源代码可以看到RSA公钥是通过请求服务…

【JavaEE】网络(2)

一、网络编程套接字 1.1 基础概念 【网络编程】指网络上的主机&#xff0c;通过不同的进程&#xff0c;以编程的方式实现网络通信&#xff1b;当然&#xff0c;我们只要满足进程不同就行&#xff0c;所以即便是同一个主机&#xff0c;只要是不同进程&#xff0c;基于网络来传…

《操作系统 - 清华大学》7 -1:全局页面置换算法:局部页替换算法的问题、工作集模型

文章目录 1. 局部页替换算法的问题2. 全局置换算法的工作原理3. 工作集模式3.1 工作集3.2 工作集的变化 4 常驻集 1. 局部页替换算法的问题 局部页面置换算法 OPT&#xff0c;FIFO&#xff0c;LRU&#xff0c;Clock 等等&#xff0c;这些算法都是针对一个正在运行的程序来讲的…

SpringCloud和Nacos的基础知识和使用

1.什么是SpringCloud ​   什么是微服务&#xff1f; ​   假如我们需要搭建一个网上购物系统&#xff0c;那么我们需要哪些功能呢&#xff1f;商品中心、订单中心和客户中心等。 ​   当业务功能较少时&#xff0c;我们可以把这些功能塞到一个SpringBoot项目中来进行…

LLMs之APE:基于Claude的Prompt Improver的简介、使用方法、案例应用之详细攻略

LLMs之APE&#xff1a;基于Claude的Prompt Improver的简介、使用方法、案例应用之详细攻略 目录 Prompt Improver的简介 0、背景痛点 1、优势 2、实现思路 Prompt优化 示例管理 提示词评估 Prompt Improver的使用方法 1、使用方法 Prompt Improver的案例应用 1、Kap…

CMake简单使用(二)

目录 五、scope 作用域5.1 作用域的类型5.1.1 全局作用域5.1.2 目录作用域5.1.3 函数作用域 六、宏6.1 基本语法6.2 演示代码 七、CMake构建项目7.1 全局变量7.2 写入源码路径7.3 调用子目录cmake脚本7.4 CMakeLists 嵌套(最常用) 八、CMake 与库8.1 CMake生成动静态库8.1.1 动…

ASP.NET |日常开发中读写XML详解

ASP.NET &#xff5c;日常开发中读写XML详解 前言一、XML 概述1.1 定义和结构1.2 应用场景 二、读取 XML 文件2.1 使用XmlDocument类&#xff08;DOM 方式&#xff09;2.2 使用XmlReader类&#xff08;流方式&#xff09; 三、写入 XML 文件3.1 使用XmlDocument类3.2 使用XmlWr…

自动化测试之单元测试框架

单元测试框架 一、单元测试的定义 1&#xff1a;什么是单元测试&#xff1f; 还记不记得我们软件测试学习的时候&#xff0c;按照定义&#xff1a;单元测试就是对单个模块或者是单个函数进行测试&#xff0c;一般是开发做的&#xff0c;按照阶段来分&#xff0c;一般就是单元…

JAVA爬虫获取1688关键词接口

以下是使用Java爬虫获取1688关键词接口的详细步骤和示例代码&#xff1a; 一、获取API接口访问权限 要使用1688关键词接口&#xff0c;首先需要获取API的使用权限&#xff0c;并了解接口规范。以下是获取API接口的详细步骤&#xff1a; 注册账号&#xff1a;在1688平台注册一…

【游戏设计原理】8 - 霍华德的隐匿性游戏设计法则

1. 霍华德的隐匿性游戏设计法则 霍华德的隐匿性游戏设计法则的核心思想是&#xff1a;“秘密的重要性与其表面上的无辜性和完整度成正比”。这意味着&#xff0c;当游戏开始时&#xff0c;设计上越是简洁、无害、直观的元素&#xff0c;隐藏的深层意义和转折就会显得更加震撼和…

k8s中用filebeat文件如何收集不同service的日志

以下是一个详细的从在 Kubernetes 集群中部署 Filebeat&#xff0c;到实现按web-oper、web-api微服务分离日志并存储到不同索引的完整方案&#xff1a; 理解需求&#xff1a;按服务分离日志索引 在 Kubernetes 集群中&#xff0c;有web-oper和web-api两种微服务&#xff0c;希…

前端退出对话框也就是点击右上角的叉,显示灰色界面,已经解决

文章目录 遇到一个前端bug&#xff0c;点击生成邀请码 打开对话框 然后我再点击叉号&#xff0c;退出对话框&#xff0c;虽然退出了对话框&#xff0c;但是显示灰色界面。如下图&#xff1a; 导致界面就会失效&#xff0c;点击任何地方都没有反应。 发现是如下代码的问题&am…