面试官:小伙子,你给我简单说一下RocketMQ 整合 Spring Boot吧
前言
在使用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吧的更多相关文章
- 原创 | 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration
GitHub 3.7k Star 的Java工程师成神之路 ,不来了解一下吗? GitHub 3.7k Star 的Java工程师成神之路 ,真的不来了解一下吗? GitHub 3.7k Star 的 ...
- 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration
现在大部分的Spring项目都采用了基于注解的配置,采用了@Configuration 替换标签的做法.一行简单的注解就可以解决很多事情.但是,其实每一个注解背后都有很多值得学习和思考的内容.这些思考 ...
- 一个简单且易上手的 Spring boot 后台管理框架-->EL-ADMIN
一个简单且易上手的 Spring boot 后台管理框架 后台源码 前台源码
- 关于MongoDB的简单理解(三)--Spring Boot篇
一.前言 Spring Boot集成MongoDB非常简单,主要为加依赖,加配置,编码. 二.说明 环境说明: JDK版本为15(1.8+即可) Spring Boot 2.4.1 三.集成步骤 3. ...
- 手撕面试官系列(二):开源框架面试题Spring+SpringMVC+MyBatis
文章首发于今日头条:https://www.toutiao.com/i6712324863006081549/ 前言 跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽.切不可跟风,看 ...
- 跟面试官讲Binder(零)
面试的时候,面试官问你说,简单说一下Android的Binder机制,你会怎么回答? 我想,我会这么说. 在Android启动的时候,Zygote进程孵化出第一个子进程叫SystemServer,而在 ...
- Redis——面试官考题
总结: 本文在一次面试的过程中讲述了 Redis 是什么,Redis 的特点和功能,Redis 缓存的使用,Redis 为什么能这么快,Redis 缓存的淘汰策略,持久化的两种方式,Redis 高可用 ...
- 《PHP程序员面试笔试宝典》——如何巧妙地回答面试官的问题?
如何巧妙地回答面试官的问题? 本文摘自<PHP程序员面试笔试宝典> 所谓"来者不善,善者不来",程序员面试中,求职者不可避免地需要回答面试官各种"刁钻&quo ...
- 吐血整理 20 道 Spring Boot 面试题,我经常拿来面试别人!
面试了一些人,简历上都说自己熟悉 Spring Boot, 或者说正在学习 Spring Boot,一问他们时,都只停留在简单的使用阶段,很多东西都不清楚,也让我对面试者大失所望. 下面,我给大家总结 ...
随机推荐
- 关于vm.min_free_kbytes的合理设置推测
前言 之前系统出现过几次hung住的情况,没有oom,也没有其它内存相关的信息,而linux设计就是去尽量吃满内存,然后再回收清理的机制 探讨 目前这个参数还没有找到合适的处理这个预留的参数,一般也没 ...
- SQL Server 批量生成数据库内多个表的表结构
在遇到大型数据库的时候,一个数据库内存在大量的数据表,如果要生成多个表的表结构,一个表的检索,然后右键Create出来,太麻烦了. 下面我介绍下批量选择并生成多个表的表结构快捷方式,仅供参考. 第一步 ...
- ABBYY FineReader 14新建任务窗口给我们哪些帮助?
当您启动ABBYY FineReader时, 新任务 将打开一个窗口,在其中您可以轻松打开.扫描.创建或对比文档. 如果您没有看到此 内置任务 窗口(比如,如果您关闭了该窗口,或者您通过在 Windo ...
- 攻克solo第六课(大调音阶与真的爱你)
在本期文章中,笔者将通过guitar pro7和大家分享大调音阶的知识. 不知道大家有没有试着使用my song book里面的谱子,反正笔者是觉得赚大了,并且找了囊括民谣.爵士到摇滚在内不同风格的谱 ...
- 用思维导图软件MindManager整理假期
今天带大家使用MindManager2020软件构建出2020年的节假日思维导图. 既然是做2020年的节假日思维导图,那么有个MindManager技巧就是,关于这一类思维导图我们都可以选择时间线导 ...
- JS&Swift相互交互
加载本地HTML文件 x override func loadView() { super.loadView() let conf = WKWebViewCon ...
- docker提示容器已存在
docker ps -a docker rm 容器id 重启启动
- PHP AES加密封装类
简介 PHP AES 加密解密常用封装类 使用方式 $key = 123; $aes = new Aes($key); $data = ['a' => 1]; $aes->decrypt( ...
- 硬RAID和软RAID
RAID简介: RAID是 Redundant Array of Independent Disks的简写,意为独立磁盘冗余阵列,简称磁盘阵列.基本思想是把多个相对便宜的硬盘结合起来,称为一个磁盘阵列 ...
- fist-第四天冲刺随笔
这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...