【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版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
相关文章
|
1月前
|
存储 消息中间件 安全
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
【10月更文挑战第9天】本文介绍了如何利用JUC组件实现Java服务与硬件通过MQTT的同步通信(RRPC)。通过模拟MQTT通信流程,使用`LinkedBlockingQueue`作为消息队列,详细讲解了消息发送、接收及响应的同步处理机制,包括任务超时处理和内存泄漏的预防措施。文中还提供了具体的类设计和方法实现,帮助理解同步通信的内部工作原理。
JUC组件实战:实现RRPC(Java与硬件通过MQTT的同步通信)
|
18天前
|
Java Linux Windows
如何查看已安装的 Java 版本
要查看已安装的 Java 版本,打开命令提示符或终端,输入 `java -version`,回车后即可显示当前系统中 Java 的版本信息。
|
18天前
|
Ubuntu Java Linux
如何检查 Java 版本是否兼容
要检查Java版本是否兼容,可在命令行输入“java -version”查看当前安装的Java版本,然后对比目标应用所需的Java版本,确保其满足要求。
|
1月前
|
缓存 Java Maven
java: 警告: 源发行版 11 需要目标发行版 11 无效的目标发行版: 11 jdk版本不符,项目jdk版本为其他版本
如何解决Java项目中因JDK版本不匹配导致的编译错误,包括修改`pom.xml`文件、调整项目结构、设置Maven和JDK版本,以及清理缓存和重启IDEA。
46 1
java: 警告: 源发行版 11 需要目标发行版 11 无效的目标发行版: 11 jdk版本不符,项目jdk版本为其他版本
|
1月前
|
Java Docker 容器
java版本学习网站又添加了一个libgdx模块
java版本学习网站之前添加了docker,想了想还是再把libgdx添加进去吧。
29 3
|
1月前
|
分布式计算 Java Hadoop
Hadoop-30 ZooKeeper集群 JavaAPI 客户端 POM Java操作ZK 监听节点 监听数据变化 创建节点 删除节点
Hadoop-30 ZooKeeper集群 JavaAPI 客户端 POM Java操作ZK 监听节点 监听数据变化 创建节点 删除节点
62 1
|
1月前
|
消息中间件 存储 JSON
rabbitmq基础教程(ui,java,springamqp)
本文提供了RabbitMQ的基础教程,包括如何使用UI创建队列和交换机、Java代码操作RabbitMQ、Spring AMQP进行消息发送和接收,以及如何使用不同的交换机类型(fanout、direct、topic)进行消息路由。
24 0
rabbitmq基础教程(ui,java,springamqp)
|
1月前
|
Java Maven Spring
查看springboot版本支持最高的java版本
截至最近更新,Spring Boot 3.0及以上版本支持的最高Java版本为Java 17。鉴于技术的不断演进,建议直接参考Spring Boot的官方文档获取最准确的支持信息,因为这些版本兼容性可能会随着新版本的发布而有所变化。选择与你的Spring Boot版本相匹配的Java版本,可以确保充分利用框架特性,同时保证项目的稳定性和前瞻性。
46 0
|
2月前
|
Java
java版本详解
java版本详解
|
1月前
|
Java C#
Java的监听处理事件--小球移动案例
Java的监听处理事件--小球移动案例
13 0