【Java】最新版本SpringCloudStream整合RocketMQ实现单项目中事件的发布与监听

简介: 【Java】最新版本SpringCloudStream整合RocketMQ实现单项目中事件的发布与监听

前言

SpringCloud项目中整合RocketMQ是为了削峰填谷。

这里我使用RocketMQ的作用用于接收项目中产生的消息,然后异步的发送邮件给客户,这是这个项目的产生的背景。

依赖配置

<dependencies>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <!-- 引入基于 RocketMQ 的 Spring Cloud Bus 的实现的依赖,并实现对其的自动配置 -->
            <artifactId>spring-cloud-starter-bus-rocketmq</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.rocketmq</groupId>
                    <artifactId>rocketmq-client</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.rocketmq</groupId>
                    <artifactId>rocketmq-acl</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <!-- 引入基于 RocketMQ 的 Spring Cloud Bus 的实现的依赖,并实现对其的自动配置 -->
            <artifactId>spring-cloud-starter-bus-rocketmq</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-acl</artifactId>
            <version>4.9.4</version>
        </dependency>
    </dependencies>

项目导入上面依赖之后即可开始代码的编写

代码

然后让我们先看一眼配置文件

# Tomcat
server:
  port: 9201
# Spring
spring:
  application:
    # 应用名称
    name: towelove-system
  profiles:
    # 环境配置
    active: dev
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: localhost:8848
      config:
        # 配置中心地址
        server-addr: localhost:8848
        # 配置文件格式
        file-extension: yaml
        # 共享配置
        shared-configs[0]:
          data-id: towelove-base-dev.yaml
          refresh: true
        shared-configs[1]:
          data-id: towelove-mysql-dev.yaml
          refresh: true
        shared-configs[2]:
          data-id: towelove-redis-dev.yaml
          refresh: true
    # Spring Cloud Stream 配置项,对应 BindingServiceProperties 类
    stream:
      function:
        definition: mailSendConsumer;sendSmsToAdmin;sendSmsToUser; # 需要确保消费者类的名称和这里一样
      # Binding 配置项,对应 BindingProperties Map
      bindings:
        sendSmsToAdmin-out-0: # 配置生产者
          destination: admin_sms_send
        sendSmsToAdmin-in-0:
          destination: admin_sms_send
          group: system_sms_send_consumer_group
        sendSmsToUser-out-0: # 配置生产者
          destination: admin_sms_send
        sendSmsToUser-in-0:
          destination: admin_sms_send
          group: system_sms_send_consumer_group
#        smsSendConsumer-in-0: # 配置消费者
#          destination: admin_sms_send
#          group: system_sms_send_consumer_group
#        smsSend-out-1:
#          destination: user_sms_send
#        smsSendConsumer-in-1:
#          destination: user_sms_send
#          group: system_sms_send_consumer_group
        mailSend-out-0:
          destination: system_mail_send
        mailSendConsumer-in-0: # 需要确保消费者类的名称和这里一样
          destination: system_mail_send
          group: system_mail_send_consumer_group
      # Spring Cloud Stream RocketMQ 配置项
      rocketmq:
        # RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类
        binder:
          name-server: 192.168.146.115:9876 # RocketMQ Namesrv 地址
        #          access-key: # 用户名
        #          secret-key:  # 密码
        default: # 默认 bindings 全局配置
          producer: # RocketMQ Producer 配置项,对应 RocketMQProducerProperties 类
            group: system_producer_group # 生产者分组
            send-type: SYNC # 发送模式,SYNC 同步
              # 如果你项目里只对接一个中间件,那么不用定义binders
              # 当系统要定义多个不同消息中间件的时候,使用binders定义
              #      binders:
              #        my-rocketmq:
              #          type: rocketmq
              #          environment:
              #            rocketmq:
              #              name-server: 192.168.146.115:9876
              #          access-key: # 用户名
            #          secret-key:  # 密码
    # Spring Cloud Bus 配置项,对应 BusProperties 类
    bus:
      enabled: true # 是否开启,默认为 true
      id: ${spring.application.name}:${server.port} # 编号,Spring Cloud Alibaba 建议使用“应用:端口”的格式
      destination: springCloudBus # 目标消息队列,默认为 springCloudBus

这里我截取了比较重要的配置,然后下面进行配置的讲解

首先就是我写了特别多注释的一个spring.cloud.stream.function.definition

这个东西是什么作用呢?

我的理解是,它用来声明你当前项目中的消费者,以及消费者类中的方法。

然后就是spring.cloud.stream.bindings中的好多个xxx-out-0和xxx-in-0

其中out对应的项目的输出,也就是消息的产生,对应的就是项目中的生产者,生产者发送消息的需要指定对应的信道,也就是你要告诉他往哪里发,其实就是对应的broker(再RocketMQ里面是这样子的),并且设定你发往的这个broker对应的topic,也就是destination。

那么同理,当生产者吧消息发送到broker中对应的topic后,我们就需要消费者去消费这个消息了。

那么此时就是使用in标签。

in标签里面的destination表示的也就是当前消费者需要去消费哪一个topic里面的消息。

你可能有一个疑问就是,那么为什么不用去指定对应的broker呢?

下面就是讲解这个in和out标签的声明的规则。

其实这也是一种约定优于配置的思想。

其中functionName就是你的消费者的类名或者你要提供消费的方法。

在命名规则的最后还有一个 index,它是 input 和 output 的序列,如果同一个 function name 只有一个 output 和一个 input,那么这个 index 永远都是 0。而如果你需要为一个 function 添加多个 input 和 output,就需要使用 index 变量来区分每个生产者消费者了。

Input 信道(消费者):< functionName > - in - < index >;

Output 信道(生产者):< functionName > - out - < index >。

讲解完这些,你大概就理解了这里的代码是为什么这么编写了。

那么下面我引入具体的业务代码。

我们从底层向上。

首先是消息的实体类。

@Data
public class SmsSendMessage {
    /**
     * 邮件日志编号
     */
    @NotNull(message = "邮件日志编号不能为空")
    private Long logId;
    /**
     * 接收邮件地址
     */
    @NotNull(message = "电话号码不能为空")
    private String phonenumber;
    /**
     * 邮件账号编号
     */
    @NotNull(message = "邮件账号编号不能为空")
    private Long accountId;
    /**
     * 邮件发件人
     */
    private String nickname;
    /**
     * 邮件标题
     */
    @NotEmpty(message = "邮件标题不能为空")
    private String title;
    /**
     * 邮件内容
     */
    @NotEmpty(message = "邮件内容不能为空")
    private String content;
    private Boolean isHtml;
    private File[] files;
}

这个是消息的生产者

@Slf4j
@Service
public class SmsProducer {
    @Autowired
    private StreamBridge streamBridge;
    public void sendSmsToAdmin(SmsSendMessage message) {
        log.info("要发送的短信内容为: {}", message);
        streamBridge.send("sendSmsToAdmin-out-0", message);
    }
    public void sendSmsToUser(Long userId,Long accountId) {
        log.info("要发送的短信内容为: {}", "userId:"+userId+"accountId:"+accountId);
        streamBridge.send("sendSmsToUser-out-0",  "userId:"+userId+"  accountId:"+accountId);
    }
}

然后就是控制层

@RestController
@RequestMapping("/sys/sms")
public class SmsController {
    @Autowired
    private SmsProducer smsProducer;
    @PostMapping("/send/admin")
    public R<Boolean> sendSmsToAdmin(@RequestBody @Valid SmsSendMessage message){
        smsProducer.sendSmsToAdmin(message);
        return R.ok();
    }
    @PostMapping("/send/user")
    public R<Boolean> sendSmsToUser(@RequestParam("userId")Long userId,
                                    @RequestParam("accountId")Long accountId){
        smsProducer.sendSmsToUser(userId,accountId);
        return R.ok();
    }
}

然后下面是事件消费者的第一种写法

@Component
@Slf4j
public class SmsSendConsumer //implements Consumer<SmsSendMessage>
{
    //@Override
    //public void accept(SmsSendMessage message) {
    //    System.out.println(message);
    //}
    @Bean
    public Consumer<String> sendSmsToAdmin() {
        return reqest -> {
            log.info("received: {} ", reqest);
        };
    }
    @Bean
    public Consumer<String> sendSmsToUser(){
        return request -> {
            log.info("received: {}", request);
            List<Long> params = Arrays.stream(request.split(","))
                    .map(Long::valueOf)
                    .collect(Collectors.toList());
            System.out.println(params);
        };
    }
}

简单的介绍一下代码的逻辑,

其实就是我们向控制层发送一个请求并且携带上一些参数之后,控制层让生产者发送一个消息到对应的消息队列中。

发现了吗,这里消息的生产者发送的消息的目的地,就是我们设定的out标签。

那么消费者如何知道要去消费消息呢?

这就是为什么上面我说function.definition和in标签的作用了。

in标签这里的前缀就是我们的方法名,也就是对应的broker中的topic有消息后,对应的消费者会把消息拉过来,然后进行消费,而他之所以能知道要去消费哪一个消息也就是因为这里的绑定好的原因。

所以如果你一个类中声明了多个的消费方法,只需要再function.definition这个地方声明出你方法的名称,并且再代码里面使用@Bean的方式去声明出对应的方法即可

也就是如下图一样。

那么好奇的你可能会发现,这样子可以定义多个方法,还挺不错的,就是好像有点麻烦欸,要写的东西一下子就多了。

所以,如果你的消费者类只有一个方法,也就是你当前要消费的消费者只需要提供唯一的方法,那么我们可以把function.definition这里的方法名编写为消费者类的名称。

也就是下面这种代码的方式

而我们的生产者还是一样,只要确保其发送消息的信道是确定的即可

那么以这两种方式,如果你的消费者需要提供多个方法,那么就使用第一种方式,而如果你的消费者是单一的,只需要提供某一种方法,那么直接使用第二种方法去实现某个类即可。

当然,两种方式可以混合在一起实现

如果你在你的代码中出现了下图的问题

可以查看我下面这篇文章

解决上图的问题

类似的springcloudstream整合rocketmq的问题可以私信我一起研究


相关实践学习
快速体验阿里云云消息队列RocketMQ版
本实验将带您快速体验使用云消息队列RocketMQ版Serverless系列实例进行获取接入点、创建Topic、创建订阅组、收发消息、查看消息轨迹和仪表盘。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
16天前
|
安全 Java API
Java Web 在线商城项目最新技术实操指南帮助开发者高效完成商城项目开发
本项目基于Spring Boot 3.2与Vue 3构建现代化在线商城,涵盖技术选型、核心功能实现、安全控制与容器化部署,助开发者掌握最新Java Web全栈开发实践。
195 1
|
2月前
|
前端开发 Java API
2025 年 Java 全栈从环境搭建到项目上线实操全流程指南:Java 全栈最新实操指南(2025 版)
本指南涵盖2025年Java全栈开发核心技术,从JDK 21环境搭建、Spring Boot 3.3实战、React前端集成到Docker容器化部署,结合最新特性与实操流程,助力构建高效企业级应用。
502 1
|
4月前
|
前端开发 JavaScript Java
Java 学习路线规划及项目案例中的技术栈应用解析
内容包括:**Java 17核心特性**(如sealed class、record)与模块化开发;Spring Boot 3 + Spring Cloud微服务架构,涉及响应式编程(WebFlux)、多数据库持久化(JPA、R2DBC、MongoDB);云原生技术**如Docker、Kubernetes及CI/CD流程;性能优化(GraalVM Native Image、JVM调优);以及前后端分离开发(Vue 3、Spring Boot集成)。通过全栈电商平台项目实战,掌握从后端服务(用户、商品、订单)到前端应用(Vue 3、React Native)的全流程开发。
182 9
|
12天前
|
安全 架构师 Java
Java LTS版本进化秀:从8到21的欢乐升级之旅
困惑于Java版本选择?轻松幽默地穿越Java LTS版本时光隧道,掌握从Java 8到21的关键特性。通过一家初创公司的系统升级故事,直观了解每个版本如何解决代码冗余、性能瓶颈等开发痛点,助你在技术选型中做出明智决策。
|
2月前
|
JavaScript Java 微服务
现代化 Java Web 在线商城项目技术方案与实战开发流程及核心功能实现详解
本项目基于Spring Boot 3与Vue 3构建现代化在线商城系统,采用微服务架构,整合Spring Cloud、Redis、MySQL等技术,涵盖用户认证、商品管理、购物车功能,并支持Docker容器化部署与Kubernetes编排。提供完整CI/CD流程,助力高效开发与扩展。
324 63
|
14天前
|
IDE 安全 Java
Lombok 在企业级 Java 项目中的隐性成本:便利背后的取舍之道
Lombok虽能简化Java代码,但其“魔法”特性易破坏封装、影响可维护性,隐藏调试难题,且与JPA等框架存在兼容风险。企业级项目应优先考虑IDE生成、Java Records或MapStruct等更透明、稳健的替代方案,平衡开发效率与系统长期稳定性。
107 1
|
16天前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
2月前
|
Cloud Native Java API
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
Java Spring框架技术栈选和最新版本及发展史详解(截至2025年8月)-优雅草卓伊凡
293 0
|
3月前
|
安全 Java API
Java 17 及以上版本核心特性在现代开发实践中的深度应用与高效实践方法 Java 开发实践
本项目以“学生成绩管理系统”为例,深入实践Java 17+核心特性与现代开发技术。采用Spring Boot 3.1、WebFlux、R2DBC等构建响应式应用,结合Record类、模式匹配、Stream优化等新特性提升代码质量。涵盖容器化部署(Docker)、自动化测试、性能优化及安全加固,全面展示Java最新技术在实际项目中的应用,助力开发者掌握现代化Java开发方法。
130 1
|
3月前
|
安全 Java 测试技术
Java 大学期末实操项目在线图书管理系统开发实例及关键技术解析实操项目
本项目基于Spring Boot 3.0与Java 17,实现在线图书管理系统,涵盖CRUD操作、RESTful API、安全认证及单元测试,助力学生掌握现代Java开发核心技能。
111 1