前言

在使用SpringBoot的starter集成包时,要特别注意版本。因为SpringBoot集成RocketMQ的starter依赖是由Spring社区提供的,目前正在快速迭代的过程当中,不同版本之间的差距非常大,甚至基础的底层对象都会经常有改动。例如如果使用rocketmq-spring-boot-starter:2.0.4版本开发的代码,升级到目前最新的rocketmq-spring-boot-starter:2.1.1后,基本就用不了了

应用结构

TestController: 测试入口, 有基本消息测试和事务消息测试

TopicListener: 是监听"topic"这个主题的普通消息监听器

TopicTransactionListener: 是监听"topic"这个主题的事务消息监听器, 和TopicTransactionRocketMQTemplate绑定(一一对应关系)

Customer: 是测试消息体的一个entity对象

TopicTransactionRocketMQTemplate: 是扩展自RocketMQTemplate的另一个RocketMQTemplate, 专门用来处理某一个业务流程, 和TopicTransactionListener绑定(一一对应关系)

pom.xml

org.apache.rocketmq:rocketmq-spring-boot-starter:2.1.1, 引用的springboot版本是2.0.5.RELEASE

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mrathena.middle.ware</groupId>
<artifactId>rocket.mq.springboot</artifactId>
<version>1.0.0</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.1.1</version>
<!-- 屏蔽旧版本的springboot, 引用的springboot版本是2.0.5.RELEASE -->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

application.yml

server:
servlet:
context-path:
port: 80
rocketmq:
name-server: 116.62.162.48:9876
producer:
group: producer

Customer

package com.mrathena.rocket.mq.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; @Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
private String username;
private String nickname;
}

生产者 TestController

package com.mrathena.rocket.mq.controller;

import com.mrathena.rocket.mq.configuration.TopicTransactionRocketMQTemplate;
import com.mrathena.rocket.mq.entity.Customer;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendCallback;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.core.MessagePostProcessor;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import java.util.HashMap;
import java.util.Map; @Slf4j
@RestController
@RequestMapping("test")
public class TestController { private static final String TOPIC = "topic"; @Autowired
private RocketMQTemplate rocketMQTemplate;
@Autowired
private TopicTransactionRocketMQTemplate topicTransactionRocketMQTemplate; @GetMapping("base")
public Object base() {
// destination: topic/topic:tag, topic或者是topic拼接tag的整合体
// payload: 荷载即消息体
// message: org.springframework.messaging.Message, 是Spring自己封装的类, 和RocketMQ的Message不是一个类, 里面没有tags/keys等内容
rocketMQTemplate.send(TOPIC, MessageBuilder.withPayload("你好").setHeader("你是谁", "你猜").build());
// tags null
rocketMQTemplate.convertAndSend(TOPIC, "tag null");
// tags empty, 证明 tag 要么有值要么null, 不存在 empty 的 tag
rocketMQTemplate.convertAndSend(TOPIC + ":", "tag empty ?");
// 只有 tag 没有 key
rocketMQTemplate.convertAndSend(TOPIC + ":a", "tag a");
rocketMQTemplate.convertAndSend(TOPIC + ":b", "tag b");
// 有 property, 即 RocketMQ 基础 API 里面, Message(String topic, String tags, String keys, byte[] body) 里面的 key
// rocketmq-spring-boot-starter 把 userProperty 和其他的一些属性都糅合在 headers 里面可, 具体可以参考 org.apache.rocketmq.spring.support.RocketMQUtil.addUserProperties
// 获取某个自定义的属性的时候, 直接 headers.get("自定义属性key") 就可以了
Map<String, Object> properties = new HashMap<>();
properties.put("property", 1);
properties.put("another-property", "你好");
rocketMQTemplate.convertAndSend(TOPIC, "property 1", properties);
rocketMQTemplate.convertAndSend(TOPIC + ":a", "tag a property 1", properties);
rocketMQTemplate.convertAndSend(TOPIC + ":b", "tag b property 1", properties);
properties.put("property", 5);
rocketMQTemplate.convertAndSend(TOPIC, "property 5", properties);
rocketMQTemplate.convertAndSend(TOPIC + ":a", "tag a property 5", properties);
rocketMQTemplate.convertAndSend(TOPIC + ":c", "tag c property 5", properties); // 消息后置处理器, 可以在发送前对消息体和headers再做一波操作
rocketMQTemplate.convertAndSend(TOPIC, "消息后置处理器", new MessagePostProcessor() {
/**
* org.springframework.messaging.Message
*/
@Override
public Message<?> postProcessMessage(Message<?> message) {
Object payload = message.getPayload();
MessageHeaders messageHeaders = message.getHeaders();
return message;
}
}); // convertAndSend 底层其实也是 syncSend
// syncSend
log.info("{}", rocketMQTemplate.syncSend(TOPIC, "sync send"));
// asyncSend
rocketMQTemplate.asyncSend(TOPIC, "async send", new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("onSuccess");
} @Override
public void onException(Throwable e) {
log.info("onException");
}
});
// sendOneWay
rocketMQTemplate.sendOneWay(TOPIC, "send one way"); // 这个我还是不太清楚是干嘛的? 跑的时候会报错!!!
// Object receive = rocketMQTemplate.sendAndReceive(TOPIC, "你好", String.class);
// log.info("{}", receive); return "success";
} @GetMapping("transaction")
public Object transaction() {
Message<Customer> message = MessageBuilder.withPayload(new Customer("mrathena", "你是谁")).build();
// 这里使用的是通过 @ExtRocketMQTemplateConfiguration(group = "anotherProducer") 扩展出来的另一个 RocketMQTemplate
log.info("{}", topicTransactionRocketMQTemplate.sendMessageInTransaction(TOPIC, message, null));
log.info("{}", topicTransactionRocketMQTemplate.sendMessageInTransaction(TOPIC + ":tag-a", message, null));
return "success";
} }

配置 TopicTransactionRocketMQTemplate

package com.mrathena.rocket.mq.configuration;

import org.apache.rocketmq.spring.annotation.ExtRocketMQTemplateConfiguration;
import org.apache.rocketmq.spring.core.RocketMQTemplate; /**
* 一个事务流程和一个RocketMQTemplate需要一一对应
* 可以通过 @ExtRocketMQTemplateConfiguration(注意该注解有@Component注解) 来扩展多个 RocketMQTemplate
* 注意: 不同事务流程的RocketMQTemplate的producerGroup不能相同
* 因为MQBroker会反向调用同一个producerGroup下的某个checkLocalTransactionState方法, 不同流程使用相同的producerGroup的话, 方法可能会调用错
*/
@ExtRocketMQTemplateConfiguration(group = "anotherProducer")
public class TopicTransactionRocketMQTemplate extends RocketMQTemplate {}

消费者 TopicListener

package com.mrathena.rocket.mq.listener;

import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.MessageModel;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component; /**
* 最简单的消费者例子
* topic: 主题
* consumerGroup: 消费者组
* selectorType: 过滤方式, TAG:标签过滤,仅支持标签, SQL92:SQL过滤,支持标签和属性
* selectorExpression: 过滤表达式, 根据selectorType定, TAG时, 写标签如 "a || b", SQL92时, 写SQL表达式
* consumeMode: CONCURRENTLY:并发消费, ORDERLY:顺序消费
* messageModel: CLUSTERING:集群竞争消费, BROADCASTING:广播消费
*/
@Slf4j
@Component
@RocketMQMessageListener(topic = "topic",
// 只过滤tag, 不管headers中的key和value
// selectorType = SelectorType.TAG,
// 必须指定selectorExpression, 可以过滤tag和headers中的key和value
// selectorType = SelectorType.SQL92,
// 不限tag
// selectorExpression = "*",
// 不限tag, 和 * 一致
// selectorExpression = "",
// 只要tag为a的消息
// selectorExpression = "a",
// 要tag为a或b的消息
// selectorExpression = "a || b",
// SelectorType.SQL92时, 可以跳过tag, 直接用headers里面的key和value来判断
// selectorExpression = "property = 1",
// tag不为null
// selectorExpression = "TAGS is not null",
// tag为empty, 证明tag不会是empty, 要么有值要么null
// selectorExpression = "TAGS = ''",
// SelectorType.SQL92时, 即过滤tag, 又过滤headers里面的key和value
// selectorExpression = "(TAGS is not null and TAGS = 'a') and (property is not null and property between 4 and 6)",
// 并发消费
consumeMode = ConsumeMode.CONCURRENTLY,
// 顺序消费
// consumeMode = ConsumeMode.ORDERLY,
// 集群消费
messageModel = MessageModel.CLUSTERING,
// 广播消费
// messageModel = MessageModel.BROADCASTING,
consumerGroup = "consumer"
)
public class TopicListener implements RocketMQListener<String> {
public void onMessage(String s) {
log.info("{}", s);
}
}

消费者 TopicTransactionListener

package com.mrathena.rocket.mq.listener;

import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Component; @Slf4j
@Component
@RocketMQTransactionListener(rocketMQTemplateBeanName = "topicTransactionRocketMQTemplate")
public class TopicTransactionListener implements RocketMQLocalTransactionListener { @Override
public RocketMQLocalTransactionState executeLocalTransaction(Message message, Object o) {
// message: org.springframework.messaging.Message, 是Spring自己封装的类, 和RocketMQ的Message不是一个类, 里面没有tags/keys等内容
// 一般来说, 并不会在这里处理tags/keys等内容, 而是根据消息体中的某些字段做不同的操作, 第二个参数也可以用来传递一些数据到这里
log.info("executeLocalTransaction message:{}, object:{}", message, o);
log.info("payload: {}", new String((byte[]) message.getPayload()));
MessageHeaders headers = message.getHeaders();
log.info("tags: {}", headers.get(RocketMQHeaders.PREFIX + RocketMQHeaders.TAGS));
log.info("rocketmq_TOPIC: {}", headers.get("rocketmq_TOPIC"));
log.info("rocketmq_QUEUE_ID: {}", headers.get("rocketmq_QUEUE_ID"));
log.info("rocketmq_MESSAGE_ID: {}", headers.get("rocketmq_MESSAGE_ID"));
log.info("rocketmq_TRANSACTION_ID: {}", headers.get("rocketmq_TRANSACTION_ID"));
log.info("TRANSACTION_CHECK_TIMES: {}", headers.get("TRANSACTION_CHECK_TIMES"));
log.info("id: {}", headers.get("id"));
return RocketMQLocalTransactionState.UNKNOWN;
} @Override
public RocketMQLocalTransactionState checkLocalTransaction(Message message) {
log.info("checkLocalTransaction message:{}", message);
// 在调用了checkLocalTransaction后, 另一个常规消息监听器才能收到消息
return RocketMQLocalTransactionState.COMMIT;
}
}

最后

欢迎关注公众号:前程有光,领取一线大厂Java面试题总结+各知识点学习思维导+一份300页pdf文档的Java核心知识点总结!

面试官:小伙子,你给我简单说一下RocketMQ 整合 Spring Boot吧的更多相关文章

  1. 原创 | 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration

    GitHub 3.7k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 3.7k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 3.7k Star 的 ...

  2. 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration

    现在大部分的Spring项目都采用了基于注解的配置,采用了@Configuration 替换标签的做法.一行简单的注解就可以解决很多事情.但是,其实每一个注解背后都有很多值得学习和思考的内容.这些思考 ...

  3. 一个简单且易上手的 Spring boot 后台管理框架-->EL-ADMIN

    一个简单且易上手的 Spring boot 后台管理框架 后台源码 前台源码

  4. 关于MongoDB的简单理解(三)--Spring Boot篇

    一.前言 Spring Boot集成MongoDB非常简单,主要为加依赖,加配置,编码. 二.说明 环境说明: JDK版本为15(1.8+即可) Spring Boot 2.4.1 三.集成步骤 3. ...

  5. 手撕面试官系列(二):开源框架面试题Spring+SpringMVC+MyBatis

    文章首发于今日头条:https://www.toutiao.com/i6712324863006081549/ 前言 跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽.切不可跟风,看 ...

  6. 跟面试官讲Binder(零)

    面试的时候,面试官问你说,简单说一下Android的Binder机制,你会怎么回答? 我想,我会这么说. 在Android启动的时候,Zygote进程孵化出第一个子进程叫SystemServer,而在 ...

  7. Redis——面试官考题

    总结: 本文在一次面试的过程中讲述了 Redis 是什么,Redis 的特点和功能,Redis 缓存的使用,Redis 为什么能这么快,Redis 缓存的淘汰策略,持久化的两种方式,Redis 高可用 ...

  8. 《PHP程序员面试笔试宝典》——如何巧妙地回答面试官的问题?

    如何巧妙地回答面试官的问题? 本文摘自<PHP程序员面试笔试宝典> 所谓"来者不善,善者不来",程序员面试中,求职者不可避免地需要回答面试官各种"刁钻&quo ...

  9. 吐血整理 20 道 Spring Boot 面试题,我经常拿来面试别人!

    面试了一些人,简历上都说自己熟悉 Spring Boot, 或者说正在学习 Spring Boot,一问他们时,都只停留在简单的使用阶段,很多东西都不清楚,也让我对面试者大失所望. 下面,我给大家总结 ...

随机推荐

  1. 用rsync备份一台linux服务器上的数据

    rsync是安装完linux后都会自带的,在机器上运行rsync命令看是否有安装即可 备份到远程服务器 这里介绍的rsync的用途是备份一台linux服务器上的数据到另外一台机器 环境 将需要备份机器 ...

  2. yum 常用命令使用

    1.向服务器上传文件或者下载文件 我们知道我们经常需要向服务器上传文件,或者从服务器下载文件,rz和sz命令可以满足我们的要求, 只不过默认情况下是不能使用的.我们需要使用yum install lr ...

  3. C语言设计模式(命令模式)

    #define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) typedef int (*parse_func)(const char *data,size_t len ...

  4. 太湖杯writeup

    CheckInGame checkInGame本题是个js游戏 设置个断点后,之后修改时间即可,然后把游戏玩完就行. ezWeb 本题是模板注入,过滤了{}和"",用︷︸和无引号的 ...

  5. [原题复现]2019上海大学生WEB-Decade(无参数RCE、Fuzz)

    简介  原题复现:  考察知识点:无参数命令执行.Fuzz  线上平台:https://buuoj.cn(北京联合大学公开的CTF平台) 榆林学院内可使用信安协会内部的CTF训练平台找到此题 环境复现 ...

  6. php获取字符串长度

    function len($zfc){ $arr = []; $len = mb_strlen($zfc); for ($i = 0; $i < $len; $i++) { array_push ...

  7. 为什么TCP连接时是三次握手,而不是两次或四次?

    TCP连接时有一个重要的任务就是服务端和客户端双方互相确认收发功能是否正常.图中步骤1,当客户端发起连接,服务端接收到请求,对于服务端来说,它此时知道客户端的发送功能和自己的接收功能是正常的. 图中步 ...

  8. 深入浅出!springboot从入门到精通,实战开发全套教程!

    前言 之前一直有粉丝想让我出一套springboot实战开发的教程,我这边总结了很久资料和经验,在最近总算把这套教程的大纲和内容初步总结完毕了,这份教程从springboot的入门到精通全部涵盖在内, ...

  9. python debug调试

    ------------恢复内容开始------------ 一.debug 1.step over f8(单步调试) 2.进入到下一个断点 3.运行到指定行 4.进入到对应的代码行,(和单步调试配合 ...

  10. 【PYTEST】第一章常用命令

    pytest入门 安装pytest 运行pytest pytest常用命令 1. 安装pytest pip install pytest 2. 运行pytest 2.1 pytest默认搜索当前目录下 ...