diff --git a/.licenserc.yaml b/.licenserc.yaml index ba91d1fa..5eec96d9 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -33,6 +33,7 @@ header: - 'src/test/**/*.log' - '*/src/test/resources/META-INF/service/*' - '*/src/main/resources/META-INF/service/*' + - '*/src/main/resources/META-INF/spring/*' - '**/*/spring.factories' - '**/target/**' - '**/*.iml' diff --git a/pom.xml b/pom.xml index f4659e2c..43e278fc 100644 --- a/pom.xml +++ b/pom.xml @@ -215,6 +215,9 @@ rocketmq-spring-boot-parent rocketmq-spring-boot rocketmq-spring-boot-starter + rocketmq-v5-client-spring-boot + rocketmq-v5-client-spring-boot-parent + rocketmq-v5-client-spring-boot-starter diff --git a/rocketmq-spring-boot-parent/pom.xml b/rocketmq-spring-boot-parent/pom.xml index 102238f2..a01a8142 100644 --- a/rocketmq-spring-boot-parent/pom.xml +++ b/rocketmq-spring-boot-parent/pom.xml @@ -27,6 +27,7 @@ rocketmq-spring-boot-parent + 2.2.4-SNAPSHOT pom RocketMQ Spring Boot Parent diff --git a/rocketmq-spring-boot-samples/pom.xml b/rocketmq-spring-boot-samples/pom.xml index 793eb5d1..7e7d7bbb 100644 --- a/rocketmq-spring-boot-samples/pom.xml +++ b/rocketmq-spring-boot-samples/pom.xml @@ -40,7 +40,7 @@ 1.8 1.8 - 2.2.3-SNAPSHOT + 2.2.4-SNAPSHOT diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/RocketMQAutoConfiguration.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/RocketMQAutoConfiguration.java index 9f10438e..a8c7379c 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/RocketMQAutoConfiguration.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/RocketMQAutoConfiguration.java @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.rocketmq.spring.autoconfigure; import org.apache.rocketmq.client.AccessChannel; diff --git a/rocketmq-v5-client-spring-boot-parent/pom.xml b/rocketmq-v5-client-spring-boot-parent/pom.xml new file mode 100644 index 00000000..a0a10fd5 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-parent/pom.xml @@ -0,0 +1,187 @@ + + + + + 4.0.0 + + org.apache.rocketmq + rocketmq-spring-all + 2.2.4-SNAPSHOT + ../pom.xml + + + rocketmq-v5-client-spring-boot-parent + pom + 2.2.4-SNAPSHOT + + rocketmq-v5-client-spring-boot-parent + rocketmq-v5-client-spring-boot-parent + + + ${project.basedir}/.. + 2.5.9 + 5.3.20 + + 2.2.4-SNAPSHOT + 5.1.0 + 1.7.25 + 2.11.1 + 1.2.83 + 4.13.2 + 5.0.5 + 1.8 + @ + + UTF-8 + UTF-8 + ${java.version} + ${java.version} + -Xdoclint:none + jacoco + + ${project.basedir}/../test/target/jacoco-it.exec + file:**/generated-sources/**,**/test/** + + + + + + org.springframework.boot + spring-boot + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-autoconfigure + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-autoconfigure-processor + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-configuration-processor + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + + org.springframework.boot + spring-boot-starter + ${spring.boot.version} + + + + org.springframework.boot + spring-boot-starter-validation + ${spring.boot.version} + + + + org.apache.rocketmq + rocketmq-v5-client-spring-boot + ${rocketmq.client.spring.boot.version} + + + + org.apache.rocketmq + rocketmq-client-java + ${rocketmq.spring.client.version} + + + org.slf4j + slf4j-api + + + + + + org.apache.rocketmq + rocketmq-acl + ${rocketmq.version} + + + + org.springframework + spring-messaging + ${spring.version} + + + + org.springframework + spring-core + ${spring.version} + + + + org.springframework + spring-context + ${spring.version} + + + + org.springframework + spring-aop + ${spring.version} + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${jackson.version} + + + + com.alibaba + fastjson + ${fastjson.version} + + + + junit + junit + ${junit.version} + + + + + diff --git a/rocketmq-v5-client-spring-boot-samples/README-CN.md b/rocketmq-v5-client-spring-boot-samples/README-CN.md new file mode 100644 index 00000000..59698fcf --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/README-CN.md @@ -0,0 +1,668 @@ + + +# Normal消息发送 + + + +### 修改application.properties + +**rocketmq.producer.topic:** 用于给生产者设置topic名称(可选,但建议使用),生产者可以在消息发布之前**预取**topic路由。
**demo.rocketmq.normal-topic:** 用户自定义消息发送的topic + +```properties +rocketmq.producer.endpoints=127.0.0.1:8081 +rocketmq.producer.topic=normalTopic +demo.rocketmq.normal-topic=normalTopic +``` + +> 注意: +> 请将上述示例配置中的127.0.0.1:8081替换成真实RocketMQ的endpoints地址与端口 + + + +### 编写代码 + +通过@Value注解引入配置文件参数,指定自定义topic
通过@Resource注解引入RocketMQClientTemplate容器
通过调用**RocketMQClientTemplate#syncSendNormalMessage**方法进行normal消息的发送(消息的参数类型可选:Object、String、byte[]、Message) + +```java +@SpringBootApplication +public class ClientProducerApplication implements CommandLineRunner { + + private static final Logger log = LoggerFactory.getLogger(ClientProducerApplication.class); + + @Value("${demo.rocketmq.normal-topic}") + private String normalTopic; + + @Resource + private RocketMQClientTemplate rocketMQClientTemplate; + + public static void main(String[] args) { + SpringApplication.run(ClientProducerApplication.class, args); + } + + @Override + public void run(String... args) throws ClientException { + testSendNormalMessage(); + } + + //Test sending normal message + void testSendNormalMessage() { + SendReceipt sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3)); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, "normal message"); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, "byte message".getBytes(StandardCharsets.UTF_8)); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, MessageBuilder. + withPayload("test message".getBytes()).build()); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + } + + @Data + @AllArgsConstructor + public class UserMeaasge implements Serializable { + private int id; + private String userName; + private Byte userAge; + } + +} +``` + + + +# FIFO消息发送 + + + +### 修改application.properties + +**rocketmq.producer.topic:** 用于给生产者设置topic名称(可选,但建议使用),生产者可以在消息发布之前**预取**topic路由。
**demo.rocketmq.fifo-topic:** 用户自定义消息发送的topic
**demo.rocketmq.message-group=group1:** 顺序消息的顺序关系通过消息组(MessageGroup)判定和识别,发送顺序消息时需要为每条消息设置归属的消息组,相同消息组的多条消息之间遵循先进先出的顺序关系,不同消息组、无消息组的消息之间不涉及顺序性。 + +```properties +rocketmq.producer.endpoints=127.0.0.1:8081 +rocketmq.producer.topic=fifoTopic +demo.rocketmq.fifo-topic=fifoTopic +demo.rocketmq.message-group=group1 +``` + +> 注意: +> 请将上述示例配置中的127.0.0.1:8081替换成真实RocketMQ的endpoints地址与端口 + + + +### 编写代码 + +通过@Value注解引入配置文件参数,指定自定义topic
通过@Resource注解引入RocketMQClientTemplate容器
通过调用**RocketMQClientTemplate#syncSendNormalMessage**方法进行fifo消息的发送(参数类型可选:Object、String、byte[]、Message)
发送fifo消息时需要设置参数:消费者组(MessageGroup) + +```java +@SpringBootApplication +public class ClientProducerApplication implements CommandLineRunner { + + private static final Logger log = LoggerFactory.getLogger(ClientProducerApplication.class); + + @Value("${demo.rocketmq.fifo-topic}") + private String fifoTopic; + + @Value("${demo.rocketmq.message-group}") + private String messageGroup; + + @Resource + private RocketMQClientTemplate rocketMQClientTemplate; + + public static void main(String[] args) { + SpringApplication.run(ClientProducerApplication.class, args); + } + + @Override + public void run(String... args) throws ClientException { + testSendFIFOMessage(); + } + + //Test sending fifo message + void testSendFIFOMessage() { + SendReceipt sendReceipt = rocketMQClientTemplate.syncSendFifoMessage(fifoTopic, new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3), messageGroup); + System.out.printf("fifoSend to topic %s sendReceipt=%s %n", fifoTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendFifoMessage(fifoTopic, MessageBuilder. + withPayload("test message".getBytes()).build(), messageGroup); + System.out.printf("fifoSend to topic %s sendReceipt=%s %n", fifoTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendFifoMessage(fifoTopic, "fifo message", messageGroup); + System.out.printf("fifoSend to topic %s sendReceipt=%s %n", fifoTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendFifoMessage(fifoTopic, "byte message".getBytes(StandardCharsets.UTF_8), messageGroup); + System.out.printf("fifoSend to topic %s sendReceipt=%s %n", fifoTopic, sendReceipt); + } + + @Data + @AllArgsConstructor + public class UserMeaasge implements Serializable { + private int id; + private String userName; + private Byte userAge; + } + +} +``` + + + +# Delay消息发送 + + + +### 修改application.properties + +**rocketmq.producer.topic:** 用于给生产者设置topic名称(可选,但建议使用),生产者可以在消息发布之前**预取**topic路由。
**demo.rocketmq.delay-topic:** 用户自定义消息发送的topic + +```class +rocketmq.producer.endpoints=127.0.0.1:8081 +rocketmq.producer.topic=delayTopic +demo.rocketmq.fifo-topic=delayTopic +``` + +> 注意: +> 请将上述示例配置中的127.0.0.1:8081替换成真实RocketMQ的endpoints地址与端口 + + + +### 编写代码 + +通过@Value注解引入配置文件参数,指定自定义topic
通过@Resource注解引入RocketMQClientTemplate容器
通过调用**RocketMQClientTemplate#syncSendNormalMessage**方法进行delay消息的发送(消息的参数类型可选:Object、String、byte[]、Message)
发送delay消息时需要指定延迟时间:DeliveryTimestamp + +```java +@SpringBootApplication +public class ClientProducerApplication implements CommandLineRunner { + + private static final Logger log = LoggerFactory.getLogger(ClientProducerApplication.class); + + @Value("${demo.rocketmq.delay-topic}") + private String delayTopic; + + @Resource + private RocketMQClientTemplate rocketMQClientTemplate; + public static void main(String[] args) { + SpringApplication.run(ClientProducerApplication.class, args); + } + + @Override + public void run(String... args) throws ClientException { + testSendDelayMessage(); + } + + //Test sending delay message + void testSendDelayMessage() { + + SendReceipt sendReceipt = rocketMQClientTemplate.syncSendDelayMessage(delayTopic, new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3), Duration.ofSeconds(10)); + System.out.printf("delaySend to topic %s sendReceipt=%s %n", delayTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendDelayMessage(delayTopic, MessageBuilder. + withPayload("test message".getBytes()).build(), Duration.ofSeconds(30)); + System.out.printf("delaySend to topic %s sendReceipt=%s %n", delayTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendDelayMessage(delayTopic, "this is my message", + Duration.ofSeconds(60)); + System.out.printf("delaySend to topic %s sendReceipt=%s %n", delayTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendDelayMessage(delayTopic, "byte messages".getBytes(StandardCharsets.UTF_8), + Duration.ofSeconds(90)); + System.out.printf("delaySend to topic %s sendReceipt=%s %n", delayTopic, sendReceipt); + } + + @Data + @AllArgsConstructor + public class UserMeaasge implements Serializable { + int id; + private String userName; + private Byte userAge; + } + +} +``` + + + +# 事务消息发送 + + + +### 修改application.properties + +**rocketmq.producer.topic:** 用于给生产者设置topic名称(可选,但建议使用),生产者可以在消息发布之前**预取**topic路由。
**demo.rocketmq.delay-topic:** 用户自定义消息发送的topic + +```class +rocketmq.producer.endpoints=127.0.0.1:8081 +rocketmq.producer.topic=transTopic +demo.rocketmq.trans-topic=transTopic +``` + +> 注意: +> 请将上述示例配置中的127.0.0.1:8081替换成真实RocketMQ的endpoints地址与端口 + + + +### 编写代码 + +通过@Value注解引入配置文件参数,指定自定义topic
通过@Resource注解引入RocketMQClientTemplate容器
通过调用**RocketMQClientTemplate#sendMessageInTransaction**方法进行事务消息的发送(消息的参数类型可选:Object、String、byte[]、Message)。
发送成功后会收到Pair类型的返回值,其左值代表返回值SendReceipt;右值代表Transaction,可以让用户根据本地事务处理结果的业务逻辑来决定commit还是rollback。
使用注解@RocketMQTransactionListener标记一个自定义类,该类必须实现RocketMQTransactionChecker接口,并重写TransactionResolution check(MessageView messageView)方法。 + +```class + void testSendTransactionMessage() throws ClientException { + Pair pair; + SendReceipt sendReceipt; + try { + pair = rocketMQClientTemplate.sendMessageInTransaction(transTopic, MessageBuilder. + withPayload(new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3)).setHeader("OrderId", 1).build()); + } catch (ClientException e) { + throw new RuntimeException(e); + } + sendReceipt = pair.getSendReceipt(); + System.out.printf("transactionSend to topic %s sendReceipt=%s %n", transTopic, sendReceipt); + Transaction transaction = pair.getTransaction(); + // executed local transaction + if (doLocalTransaction(1)) { + transaction.commit(); + } else { + transaction.rollback(); + } + } + + @RocketMQTransactionListener + static class TransactionListenerImpl implements RocketMQTransactionChecker { + @Override + public TransactionResolution check(MessageView messageView) { + if (Objects.nonNull(messageView.getProperties().get("OrderId"))) { + log.info("Receive transactional message check, message={}", messageView); + return TransactionResolution.COMMIT; + } + log.info("rollback transaction"); + return TransactionResolution.ROLLBACK; + } + } + + boolean doLocalTransaction(int number) { + log.info("execute local transaction"); + return number > 0; + } +``` + + + +# 异步消息发送 + + + +### 修改application.properties + +**rocketmq.producer.topic:** 用于给生产者设置topic名称(可选,但建议使用),生产者可以在消息发布之前**预取**topic路由。
**demo.rocketmq.delay-topic:** 用户自定义消息发送的topic + +```class +rocketmq.producer.endpoints=127.0.0.1:8081 +demo.rocketmq.fifo-topic=fifoTopic +demo.rocketmq.delay-topic=delayTopic +demo.rocketmq.normal-topic=normalTopic +demo.rocketmq.message-group=group1 +``` + +> 注意: +> 请将上述示例配置中的127.0.0.1:8081替换成真实RocketMQ的endpoints地址与端口 + + + +### 编写代码 + +```class + void testASycSendMessage() { + + CompletableFuture future0 = new CompletableFuture<>(); + CompletableFuture future1 = new CompletableFuture<>(); + CompletableFuture future2 = new CompletableFuture<>(); + ExecutorService sendCallbackExecutor = Executors.newCachedThreadPool(); + + future0.whenCompleteAsync((sendReceipt, throwable) -> { + if (null != throwable) { + log.error("Failed to send message", throwable); + return; + } + log.info("Send message successfully, messageId={}", sendReceipt.getMessageId()); + }, sendCallbackExecutor); + + future1.whenCompleteAsync((sendReceipt, throwable) -> { + if (null != throwable) { + log.error("Failed to send message", throwable); + return; + } + log.info("Send message successfully, messageId={}", sendReceipt.getMessageId()); + }, sendCallbackExecutor); + + future2.whenCompleteAsync((sendReceipt, throwable) -> { + if (null != throwable) { + log.error("Failed to send message", throwable); + return; + } + log.info("Send message successfully, messageId={}", sendReceipt.getMessageId()); + }, sendCallbackExecutor); + + CompletableFuture completableFuture0 = rocketMQClientTemplate.asyncSendNormalMessage(normalTopic, new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3), future0); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, completableFuture0); + + CompletableFuture completableFuture1 = rocketMQClientTemplate.asyncSendFifoMessage(fifoTopic, "fifo message", + messageGroup, future1); + System.out.printf("fifoSend to topic %s sendReceipt=%s %n", fifoTopic, completableFuture1); + + CompletableFuture completableFuture2 = rocketMQClientTemplate.asyncSendDelayMessage(delayTopic, + "delay message".getBytes(StandardCharsets.UTF_8), Duration.ofSeconds(10), future2); + System.out.printf("delaySend to topic %s sendReceipt=%s %n", delayTopic, completableFuture2); + } +``` + + + +# 接收消息 + + + +### Push 模式 + + + +#### 修改application.properties + +```class +demo.fifo.rocketmq.endpoints=localhost:8081 +demo.fifo.rocketmq.topic=fifoTopic +demo.fifo.rocketmq.consumer-group=fifoGroup +demo.fifo.rocketmq.tag=* +``` + +> 注意: +> 请将上述示例配置中的127.0.0.1:8081替换成真实RocketMQ的endpoints地址与端口 + + + +#### 编写代码 + +```java +@Service +@RocketMQMessageListener(endpoints = "${demo.fifo.rocketmq.endpoints:}", topic = "${demo.fifo.rocketmq.topic:}", + consumerGroup = "${demo.fifo.rocketmq.consumer-group:}", tag = "${demo.fifo.rocketmq.tag:}") +public class FifoConsumer implements RocketMQListener { + + @Override + public ConsumeResult consume(MessageView messageView) { + System.out.println("handle my fifo message:" + messageView); + return ConsumeResult.SUCCESS; + } +} +``` + + + +### Simple 模式 + + + +#### 同步订阅 + + + +##### 修改application.properties + +```class +rocketmq.simple-consumer.endpoints=localhost:8081 +rocketmq.simple-consumer.consumer-group=normalGroup +rocketmq.simple-consumer.topic=normalTopic +rocketmq.simple-consumer.tag=* +rocketmq.simple-consumer.filter-expression-type=tag +ext.rocketmq.topic=delayTopic +``` + +> 注意: +> 请将上述示例配置中的127.0.0.1:8081替换成真实RocketMQ的endpoints地址与端口 + + + +##### 编写代码 + +此时测验原始的RocketMQClientTemplate和我们拓展的ExtRocketMQTemplate是否有效: + +1. 首先定义拓展ExtRocketMQTemplate,需要加上@ExtConsumerResetConfiguration,并指定topic等关键字段。 + +```java +@ExtConsumerResetConfiguration(topic = "${ext.rocketmq.topic:}") +public class ExtRocketMQTemplate extends RocketMQClientTemplate { +} +``` + +2. receiveSimpleConsumerMessage方法消费topic=normalTopic的消息,receiveExtSimpleConsumerMessage方法消费topic=delayTopic的消息。 + +```java +@SpringBootApplication +public class ClientConsumeApplication implements CommandLineRunner { + private static final Logger log = LoggerFactory.getLogger(ClientConsumeApplication.class); + + @Resource + RocketMQClientTemplate rocketMQClientTemplate; + + @Resource(name = "extRocketMQTemplate") + RocketMQClientTemplate extRocketMQTemplate; + + public static void main(String[] args) { + SpringApplication.run(ClientConsumeApplication.class, args); + } + + @Override + public void run(String... args) throws Exception { + receiveSimpleConsumerMessage(); + receiveExtSimpleConsumerMessage(); + } + + public void receiveSimpleConsumerMessage() throws ClientException { + do { + final List messages = rocketMQClientTemplate.receive(16, Duration.ofSeconds(15)); + log.info("Received {} message(s)", messages.size()); + for (MessageView message : messages) { + log.info("receive message, topic:" + message.getTopic() + " messageId:" + message.getMessageId()); + final MessageId messageId = message.getMessageId(); + try { + rocketMQClientTemplate.ack(message); + log.info("Message is acknowledged successfully, messageId={}", messageId); + } catch (Throwable t) { + log.error("Message is failed to be acknowledged, messageId={}", messageId, t); + } + } + } while (true); + } + + public void receiveExtSimpleConsumerMessage() throws ClientException { + do { + final List messages = extRocketMQTemplate.receive(16, Duration.ofSeconds(15)); + log.info("Received {} message(s)", messages.size()); + for (MessageView message : messages) { + log.info("receive message, topic:" + message.getTopic() + " messageId:" + message.getMessageId()); + final MessageId messageId = message.getMessageId(); + try { + rocketMQClientTemplate.ack(message); + log.info("Message is acknowledged successfully, messageId={}", messageId); + } catch (Throwable t) { + log.error("Message is failed to be acknowledged, messageId={}", messageId, t); + } + } + } while (true); + } + +} +``` + + + +#### 异步订阅 + + + +##### 修改application.properties + +```class +rocketmq.simple-consumer.endpoints=localhost:8081 +rocketmq.simple-consumer.consumer-group=normalGroup +rocketmq.simple-consumer.topic=normalTopic +rocketmq.simple-consumer.tag=* +rocketmq.simple-consumer.filter-expression-type=tag +``` + + + +##### 编写代码 + +```class + public void receiveSimpleConsumerMessageAsynchronously() { + do { + int maxMessageNum = 16; + // Set message invisible duration after it is received. + Duration invisibleDuration = Duration.ofSeconds(15); + // Set individual thread pool for receive callback. + ExecutorService receiveCallbackExecutor = Executors.newCachedThreadPool(); + // Set individual thread pool for ack callback. + ExecutorService ackCallbackExecutor = Executors.newCachedThreadPool(); + CompletableFuture> future0; + try { + future0 = rocketMQClientTemplate.receiveAsync(maxMessageNum, invisibleDuration); + } catch (ClientException | IOException e) { + throw new RuntimeException(e); + } + future0.whenCompleteAsync(((messages, throwable) -> { + if (null != throwable) { + log.error("Failed to receive message from remote", throwable); + // Return early. + return; + } + log.info("Received {} message(s)", messages.size()); + // Using messageView as key rather than message id because message id may be duplicated. + final Map> map = + messages.stream().collect(Collectors.toMap(message -> message, rocketMQClientTemplate::ackAsync)); + for (Map.Entry> entry : map.entrySet()) { + final MessageId messageId = entry.getKey().getMessageId(); + final CompletableFuture future = entry.getValue(); + future.whenCompleteAsync((v, t) -> { + if (null != t) { + log.error("Message is failed to be acknowledged, messageId={}", messageId, t); + // Return early. + return; + } + log.info("Message is acknowledged successfully, messageId={}", messageId); + }, ackCallbackExecutor); + } + + }), receiveCallbackExecutor); + } while (true); + } +``` + + + +# ACL功能 + + + +## Producer端 + + + +### 修改application.properties + +```class +rocketmq.producer.endpoints=localhost:8081 +rocketmq.producer.topic=normalTopic +rocketmq.producer.access-key=yourAccessKey +rocketmq.producer.secret-key=yourSecretKey +``` + +> 注意: +> 请将上述示例配置中的127.0.0.1:8081替换成真实RocketMQ的endpoints地址与端口,并修改AccessKey与SecretKey为真实数据 + + + +### 编写代码 + +```java +@SpringBootApplication +public class ClientProducerACLApplication implements CommandLineRunner { + + @Resource + private RocketMQClientTemplate rocketMQClientTemplate; + + @Value("${demo.acl.rocketmq.normal-topic}") + private String normalTopic; + + + public static void main(String[] args) { + SpringApplication.run(ClientProducerACLApplication.class, args); + } + + @Override + public void run(String... args) throws ClientException { + testSendNormalMessage(); + } + + void testSendNormalMessage() { + SendReceipt sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3)); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, "normal message"); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, "byte message".getBytes(StandardCharsets.UTF_8)); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, MessageBuilder. + withPayload("test message".getBytes()).build()); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + } +} + +``` + + + +## Consumer端 + + + +### 修改application.properties + +```class +demo.acl.rocketmq.endpoints=localhost:8081 +demo.acl.rocketmq.topic=normalTopic +demo.acl.rocketmq.consumer-group=normalGroup +demo.acl.rocketmq.tag=* +demo.acl.rocketmq.access-key=yourAccessKey +demo.acl.rocketmq.secret-key=yourSecretKey +``` + +> 注意: +> 请将上述示例配置中的127.0.0.1:8081替换成真实RocketMQ的endpoints地址与端口,并修改AccessKey与SecretKey为真实数据 + + + +### 编写代码 + +```java +@Service +@RocketMQMessageListener(accessKey = "${demo.acl.rocketmq.access-key:}", secretKey = "${demo.acl.rocketmq.secret-key:}", endpoints = "${demo.acl.rocketmq.endpoints:}", topic = "${demo.acl.rocketmq.topic:}", + consumerGroup = "${demo.acl.rocketmq.consumer-group:}", tag = "${demo.acl.rocketmq.tag:}") +public class ACLConsumer implements RocketMQListener { + @Override + public ConsumeResult consume(MessageView messageView) { + System.out.println("handle my acl message:" + messageView); + return ConsumeResult.SUCCESS; + } +} +``` \ No newline at end of file diff --git a/rocketmq-v5-client-spring-boot-samples/pom.xml b/rocketmq-v5-client-spring-boot-samples/pom.xml new file mode 100644 index 00000000..02b00837 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/pom.xml @@ -0,0 +1,90 @@ + + + + 4.0.0 + + org.apache.rocketmq + rocketmq-v5-client-spring-boot-samples + pom + 2.2.4-SNAPSHOT + + rocketmq-v5-client-spring-boot-samples + rocketmq-v5-client-spring-boot-samples + + + rocketmq-v5-client-producer-demo + rocketmq-v5-client-consume-demo + rocketmq-v5-client-consume-acl-demo + rocketmq-v5-client-producer-acl-demo + + + + 1.8 + 1.8 + 2.2.4-SNAPSHOT + + + + + + org.apache.rocketmq + rocketmq-v5-client-spring-boot-starter + ${rocketmq-v5-client-spring-boot-starter-version} + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + + validate + validate + + src/main/resources + style/rmq_checkstyle.xml + UTF-8 + true + true + + + check + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.1.0.RELEASE + + + + repackage + + + + + + + + diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/pom.xml b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/pom.xml new file mode 100644 index 00000000..082fd24e --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + org.apache.rocketmq + rocketmq-v5-client-spring-boot-samples + 2.2.4-SNAPSHOT + + rocketmq-v5-client-consume-acl-demo + + + diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientConsumerACLApplication.java b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientConsumerACLApplication.java new file mode 100644 index 00000000..544d5903 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientConsumerACLApplication.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.samples.springboot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class ClientConsumerACLApplication { + + public static void main(String[] args) { + SpringApplication.run(ClientConsumerACLApplication.class, args); + } + +} diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ACLConsumer.java b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ACLConsumer.java new file mode 100644 index 00000000..2e583b7b --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ACLConsumer.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.samples.springboot.consumer; + + +import org.apache.rocketmq.client.annotation.RocketMQMessageListener; +import org.apache.rocketmq.client.apis.consumer.ConsumeResult; +import org.apache.rocketmq.client.apis.message.MessageView; +import org.apache.rocketmq.client.core.RocketMQListener; +import org.springframework.stereotype.Service; + +@Service +@RocketMQMessageListener(accessKey = "${demo.acl.rocketmq.access-key:}", secretKey = "${demo.acl.rocketmq.secret-key:}", + tag = "${demo.acl.rocketmq.tag:}", topic = "${demo.acl.rocketmq.topic:}", + endpoints = "${demo.acl.rocketmq.endpoints:}", consumerGroup = "${demo.acl.rocketmq.consumer-group:}") +public class ACLConsumer implements RocketMQListener { + @Override + public ConsumeResult consume(MessageView messageView) { + System.out.println("handle my acl message:" + messageView); + return ConsumeResult.SUCCESS; + } +} + diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/src/main/resources/application.properties b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/src/main/resources/application.properties new file mode 100644 index 00000000..6aea301a --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-acl-demo/src/main/resources/application.properties @@ -0,0 +1,7 @@ +demo.acl.rocketmq.endpoints=localhost:8081 +demo.acl.rocketmq.topic=normalTopic +demo.acl.rocketmq.consumer-group=normalGroup +demo.acl.rocketmq.access-key=RocketMQ +demo.acl.rocketmq.secret-key=12345678 +demo.acl.rocketmq.tag=* + diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/pom.xml b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/pom.xml new file mode 100644 index 00000000..2e549a6d --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + org.apache.rocketmq + rocketmq-v5-client-spring-boot-samples + 2.2.4-SNAPSHOT + + + rocketmq-v5-client-consume-demo + + diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientConsumeApplication.java b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientConsumeApplication.java new file mode 100644 index 00000000..8b2bd661 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientConsumeApplication.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.samples.springboot; + +import org.apache.rocketmq.client.apis.ClientException; +import org.apache.rocketmq.client.apis.message.MessageId; +import org.apache.rocketmq.client.apis.message.MessageView; +import org.apache.rocketmq.client.core.RocketMQClientTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import javax.annotation.Resource; +import java.io.IOException; +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +@SpringBootApplication +public class ClientConsumeApplication implements CommandLineRunner { + private static final Logger log = LoggerFactory.getLogger(ClientConsumeApplication.class); + + @Resource + RocketMQClientTemplate rocketMQClientTemplate; + + @Resource(name = "extRocketMQTemplate") + RocketMQClientTemplate extRocketMQTemplate; + + public static void main(String[] args) { + SpringApplication.run(ClientConsumeApplication.class, args); + } + + @Override + public void run(String... args) throws Exception { + receiveSimpleConsumerMessage(); + receiveExtSimpleConsumerMessage(); + //receiveSimpleConsumerMessageAsynchronously(); + } + + public void receiveSimpleConsumerMessage() throws ClientException { + do { + final List messages = rocketMQClientTemplate.receive(16, Duration.ofSeconds(15)); + log.info("Received {} message(s)", messages.size()); + for (MessageView message : messages) { + log.info("receive message, topic:" + message.getTopic() + " messageId:" + message.getMessageId()); + final MessageId messageId = message.getMessageId(); + try { + rocketMQClientTemplate.ack(message); + log.info("Message is acknowledged successfully, messageId={}", messageId); + } catch (Throwable t) { + log.error("Message is failed to be acknowledged, messageId={}", messageId, t); + } + } + } while (true); + } + + public void receiveExtSimpleConsumerMessage() throws ClientException { + do { + final List messages = extRocketMQTemplate.receive(16, Duration.ofSeconds(15)); + log.info("Received {} message(s)", messages.size()); + for (MessageView message : messages) { + log.info("receive message, topic:" + message.getTopic() + " messageId:" + message.getMessageId()); + final MessageId messageId = message.getMessageId(); + try { + rocketMQClientTemplate.ack(message); + log.info("Message is acknowledged successfully, messageId={}", messageId); + } catch (Throwable t) { + log.error("Message is failed to be acknowledged, messageId={}", messageId, t); + } + } + } while (true); + } + + + public void receiveSimpleConsumerMessageAsynchronously() { + do { + int maxMessageNum = 16; + // Set message invisible duration after it is received. + Duration invisibleDuration = Duration.ofSeconds(15); + // Set individual thread pool for receive callback. + ExecutorService receiveCallbackExecutor = Executors.newCachedThreadPool(); + // Set individual thread pool for ack callback. + ExecutorService ackCallbackExecutor = Executors.newCachedThreadPool(); + CompletableFuture> future0; + try { + future0 = rocketMQClientTemplate.receiveAsync(maxMessageNum, invisibleDuration); + } catch (ClientException | IOException e) { + throw new RuntimeException(e); + } + future0.whenCompleteAsync(((messages, throwable) -> { + if (null != throwable) { + log.error("Failed to receive message from remote", throwable); + // Return early. + return; + } + log.info("Received {} message(s)", messages.size()); + // Using messageView as key rather than message id because message id may be duplicated. + final Map> map = + messages.stream().collect(Collectors.toMap(message -> message, rocketMQClientTemplate::ackAsync)); + for (Map.Entry> entry : map.entrySet()) { + final MessageId messageId = entry.getKey().getMessageId(); + final CompletableFuture future = entry.getValue(); + future.whenCompleteAsync((v, t) -> { + if (null != t) { + log.error("Message is failed to be acknowledged, messageId={}", messageId, t); + // Return early. + return; + } + log.info("Message is acknowledged successfully, messageId={}", messageId); + }, ackCallbackExecutor); + } + + }), receiveCallbackExecutor); + } while (true); + } + +} diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ExtRocketMQTemplate.java b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ExtRocketMQTemplate.java new file mode 100644 index 00000000..25b66689 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ExtRocketMQTemplate.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.samples.springboot; + +import org.apache.rocketmq.client.annotation.ExtConsumerResetConfiguration; +import org.apache.rocketmq.client.core.RocketMQClientTemplate; + +@ExtConsumerResetConfiguration(topic = "${ext.rocketmq.topic:}") +public class ExtRocketMQTemplate extends RocketMQClientTemplate { +} diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/FifoConsumer.java b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/FifoConsumer.java new file mode 100644 index 00000000..ac33a726 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/FifoConsumer.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.samples.springboot.consumer; + +import org.apache.rocketmq.client.apis.consumer.ConsumeResult; +import org.apache.rocketmq.client.apis.message.MessageView; +import org.apache.rocketmq.client.annotation.RocketMQMessageListener; +import org.apache.rocketmq.client.core.RocketMQListener; +import org.springframework.stereotype.Service; + +@Service +@RocketMQMessageListener(endpoints = "${demo.fifo.rocketmq.endpoints:}", topic = "${demo.fifo.rocketmq.topic:}", + consumerGroup = "${demo.fifo.rocketmq.consumer-group:}", tag = "${demo.fifo.rocketmq.tag:}") +public class FifoConsumer implements RocketMQListener { + + @Override + public ConsumeResult consume(MessageView messageView) { + System.out.println("handle my fifo message:" + messageView); + return ConsumeResult.SUCCESS; + } +} diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/TransConsumer.java b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/TransConsumer.java new file mode 100644 index 00000000..1ef973af --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/TransConsumer.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.samples.springboot.consumer; + + +import org.apache.rocketmq.client.annotation.RocketMQMessageListener; +import org.apache.rocketmq.client.apis.consumer.ConsumeResult; +import org.apache.rocketmq.client.apis.message.MessageView; +import org.apache.rocketmq.client.core.RocketMQListener; +import org.springframework.stereotype.Service; + +@Service +@RocketMQMessageListener(endpoints = "${demo.trans.rocketmq.endpoints:}", topic = "${demo.trans.rocketmq.topic:}", + consumerGroup = "${demo.trans.rocketmq.consumer-group:}", tag = "${demo.trans.rocketmq.tag:}") +public class TransConsumer implements RocketMQListener { + public ConsumeResult consume(MessageView messageView) { + System.out.println("handle my transaction message:" + messageView); + return ConsumeResult.SUCCESS; + } +} diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/resources/application.properties b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/resources/application.properties new file mode 100644 index 00000000..39c6a0b1 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-consume-demo/src/main/resources/application.properties @@ -0,0 +1,14 @@ +rocketmq.simple-consumer.endpoints=localhost:8081 +rocketmq.simple-consumer.consumer-group=normalGroup +rocketmq.simple-consumer.topic=normalTopic +rocketmq.simple-consumer.tag=* +rocketmq.simple-consumer.filter-expression-type=tag +demo.fifo.rocketmq.endpoints=localhost:8081 +demo.fifo.rocketmq.topic=fifoTopic +demo.fifo.rocketmq.consumer-group=fifoGroup +demo.fifo.rocketmq.tag=* +demo.trans.rocketmq.endpoints=localhost:8081 +demo.trans.rocketmq.topic=transTopic +demo.trans.rocketmq.consumer-group=transGroup +demo.trans.rocketmq.tag=* +ext.rocketmq.topic=delayTopic diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/pom.xml b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/pom.xml new file mode 100644 index 00000000..c7e2cd1d --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/pom.xml @@ -0,0 +1,28 @@ + + + + 4.0.0 + + org.apache.rocketmq + rocketmq-v5-client-spring-boot-samples + 2.2.4-SNAPSHOT + + + rocketmq-v5-client-producer-acl-demo + diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientProducerACLApplication.java b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientProducerACLApplication.java new file mode 100644 index 00000000..66b4f2ca --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientProducerACLApplication.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.samples.springboot; + +import org.apache.rocketmq.client.apis.ClientException; +import org.apache.rocketmq.client.apis.producer.SendReceipt; +import org.apache.rocketmq.client.core.RocketMQClientTemplate; +import org.apache.rocketmq.samples.springboot.domain.UserMessage; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.messaging.support.MessageBuilder; + +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; + +@SpringBootApplication +public class ClientProducerACLApplication implements CommandLineRunner { + + @Resource + private RocketMQClientTemplate rocketMQClientTemplate; + + @Value("${rocketmq.producer.topic}") + private String normalTopic; + + + public static void main(String[] args) { + SpringApplication.run(ClientProducerACLApplication.class, args); + } + + @Override + public void run(String... args) throws ClientException { + testSendNormalMessage(); + } + + void testSendNormalMessage() { + SendReceipt sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3)); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, "normal message"); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, "byte message".getBytes(StandardCharsets.UTF_8)); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, MessageBuilder. + withPayload("test message".getBytes()).build()); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + } +} diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/UserMessage.java b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/UserMessage.java new file mode 100644 index 00000000..b31e48d0 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/UserMessage.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.samples.springboot.domain; + +public class UserMessage { + int id; + private String userName; + private Byte userAge; + + public int getId() { + return id; + } + + public UserMessage setId(int id) { + this.id = id; + return this; + } + + public String getUserName() { + return userName; + } + + public UserMessage setUserName(String userName) { + this.userName = userName; + return this; + } + + public Byte getUserAge() { + return userAge; + } + + public UserMessage setUserAge(Byte userAge) { + this.userAge = userAge; + return this; + } +} diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/src/main/resources/application.properties b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/src/main/resources/application.properties new file mode 100644 index 00000000..962bc26e --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-acl-demo/src/main/resources/application.properties @@ -0,0 +1,5 @@ +rocketmq.producer.endpoints=localhost:8081 +rocketmq.producer.topic=normalTopic +rocketmq.producer.access-key=RocketMQ +rocketmq.producer.secret-key=12345678 + diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/pom.xml b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/pom.xml new file mode 100644 index 00000000..d6a68592 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + org.apache.rocketmq + rocketmq-v5-client-spring-boot-samples + 2.2.4-SNAPSHOT + + + rocketmq-v5-client-producer-demo + + \ No newline at end of file diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientProducerApplication.java b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientProducerApplication.java new file mode 100644 index 00000000..f03df016 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/src/main/java/org/apache/rocketmq/samples/springboot/ClientProducerApplication.java @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.samples.springboot; + +import org.apache.rocketmq.client.apis.ClientException; +import org.apache.rocketmq.client.apis.message.MessageView; +import org.apache.rocketmq.client.apis.producer.SendReceipt; +import org.apache.rocketmq.client.apis.producer.Transaction; +import org.apache.rocketmq.client.apis.producer.TransactionResolution; + +import org.apache.rocketmq.client.annotation.RocketMQTransactionListener; +import org.apache.rocketmq.client.common.Pair; +import org.apache.rocketmq.client.core.RocketMQClientTemplate; +import org.apache.rocketmq.client.core.RocketMQTransactionChecker; +import org.apache.rocketmq.samples.springboot.domain.UserMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.messaging.support.MessageBuilder; + +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@SpringBootApplication +public class ClientProducerApplication implements CommandLineRunner { + + private static final Logger log = LoggerFactory.getLogger(ClientProducerApplication.class); + + @Resource + private RocketMQClientTemplate rocketMQClientTemplate; + + @Value("${demo.rocketmq.fifo-topic}") + private String fifoTopic; + + @Value("${demo.rocketmq.normal-topic}") + private String normalTopic; + + @Value("${demo.rocketmq.delay-topic}") + private String delayTopic; + + @Value("${demo.rocketmq.trans-topic}") + private String transTopic; + + @Value("${demo.rocketmq.message-group}") + private String messageGroup; + + + public static void main(String[] args) { + SpringApplication.run(ClientProducerApplication.class, args); + } + + @Override + public void run(String... args) throws ClientException { + testASycSendMessage(); + testSendDelayMessage(); + testSendFIFOMessage(); + testSendNormalMessage(); + testSendTransactionMessage(); + } + + void testASycSendMessage() { + + CompletableFuture future0 = new CompletableFuture<>(); + CompletableFuture future1 = new CompletableFuture<>(); + CompletableFuture future2 = new CompletableFuture<>(); + ExecutorService sendCallbackExecutor = Executors.newCachedThreadPool(); + + future0.whenCompleteAsync((sendReceipt, throwable) -> { + if (null != throwable) { + log.error("Failed to send message", throwable); + return; + } + log.info("Send message successfully, messageId={}", sendReceipt.getMessageId()); + }, sendCallbackExecutor); + + future1.whenCompleteAsync((sendReceipt, throwable) -> { + if (null != throwable) { + log.error("Failed to send message", throwable); + return; + } + log.info("Send message successfully, messageId={}", sendReceipt.getMessageId()); + }, sendCallbackExecutor); + + future2.whenCompleteAsync((sendReceipt, throwable) -> { + if (null != throwable) { + log.error("Failed to send message", throwable); + return; + } + log.info("Send message successfully, messageId={}", sendReceipt.getMessageId()); + }, sendCallbackExecutor); + + CompletableFuture completableFuture0 = rocketMQClientTemplate.asyncSendNormalMessage(normalTopic, new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3), future0); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, completableFuture0); + + CompletableFuture completableFuture1 = rocketMQClientTemplate.asyncSendFifoMessage(fifoTopic, "fifo message", + messageGroup, future1); + System.out.printf("fifoSend to topic %s sendReceipt=%s %n", fifoTopic, completableFuture1); + + CompletableFuture completableFuture2 = rocketMQClientTemplate.asyncSendDelayMessage(delayTopic, + "delay message".getBytes(StandardCharsets.UTF_8), Duration.ofSeconds(10), future2); + System.out.printf("delaySend to topic %s sendReceipt=%s %n", delayTopic, completableFuture2); + } + + void testSendDelayMessage() { + SendReceipt sendReceipt = rocketMQClientTemplate.syncSendDelayMessage(delayTopic, new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3), Duration.ofSeconds(10)); + System.out.printf("delaySend to topic %s sendReceipt=%s %n", delayTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendDelayMessage(delayTopic, MessageBuilder. + withPayload("test message".getBytes()).build(), Duration.ofSeconds(30)); + System.out.printf("delaySend to topic %s sendReceipt=%s %n", delayTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendDelayMessage(delayTopic, "this is my message", + Duration.ofSeconds(60)); + System.out.printf("delaySend to topic %s sendReceipt=%s %n", delayTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendDelayMessage(delayTopic, "byte messages".getBytes(StandardCharsets.UTF_8), + Duration.ofSeconds(90)); + System.out.printf("delaySend to topic %s sendReceipt=%s %n", delayTopic, sendReceipt); + } + + void testSendFIFOMessage() { + SendReceipt sendReceipt = rocketMQClientTemplate.syncSendFifoMessage(fifoTopic, new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3), messageGroup); + System.out.printf("fifoSend to topic %s sendReceipt=%s %n", fifoTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendFifoMessage(fifoTopic, MessageBuilder. + withPayload("test message".getBytes()).build(), messageGroup); + System.out.printf("fifoSend to topic %s sendReceipt=%s %n", fifoTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendFifoMessage(fifoTopic, "fifo message", messageGroup); + System.out.printf("fifoSend to topic %s sendReceipt=%s %n", fifoTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendFifoMessage(fifoTopic, "byte message".getBytes(StandardCharsets.UTF_8), messageGroup); + System.out.printf("fifoSend to topic %s sendReceipt=%s %n", fifoTopic, sendReceipt); + } + + void testSendNormalMessage() { + SendReceipt sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3)); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, "normal message"); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, "byte message".getBytes(StandardCharsets.UTF_8)); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + + sendReceipt = rocketMQClientTemplate.syncSendNormalMessage(normalTopic, MessageBuilder. + withPayload("test message".getBytes()).build()); + System.out.printf("normalSend to topic %s sendReceipt=%s %n", normalTopic, sendReceipt); + } + + void testSendTransactionMessage() throws ClientException { + Pair pair; + SendReceipt sendReceipt; + try { + pair = rocketMQClientTemplate.sendMessageInTransaction(transTopic, MessageBuilder. + withPayload(new UserMessage() + .setId(1).setUserName("name").setUserAge((byte) 3)).setHeader("OrderId", 1).build()); + } catch (ClientException e) { + throw new RuntimeException(e); + } + sendReceipt = pair.getSendReceipt(); + System.out.printf("transactionSend to topic %s sendReceipt=%s %n", transTopic, sendReceipt); + Transaction transaction = pair.getTransaction(); + // executed local transaction + if (doLocalTransaction(1)) { + transaction.commit(); + } else { + transaction.rollback(); + } + } + + @RocketMQTransactionListener + static class TransactionListenerImpl implements RocketMQTransactionChecker { + @Override + public TransactionResolution check(MessageView messageView) { + if (Objects.nonNull(messageView.getProperties().get("OrderId"))) { + log.info("Receive transactional message check, message={}", messageView); + return TransactionResolution.COMMIT; + } + log.info("rollback transaction"); + return TransactionResolution.ROLLBACK; + } + } + + boolean doLocalTransaction(int number) { + log.info("execute local transaction"); + return number > 0; + } + +} diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/UserMessage.java b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/UserMessage.java new file mode 100644 index 00000000..b31e48d0 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/UserMessage.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.samples.springboot.domain; + +public class UserMessage { + int id; + private String userName; + private Byte userAge; + + public int getId() { + return id; + } + + public UserMessage setId(int id) { + this.id = id; + return this; + } + + public String getUserName() { + return userName; + } + + public UserMessage setUserName(String userName) { + this.userName = userName; + return this; + } + + public Byte getUserAge() { + return userAge; + } + + public UserMessage setUserAge(Byte userAge) { + this.userAge = userAge; + return this; + } +} diff --git a/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/src/main/resources/application.properties b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/src/main/resources/application.properties new file mode 100644 index 00000000..36fc218f --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/rocketmq-v5-client-producer-demo/src/main/resources/application.properties @@ -0,0 +1,7 @@ +rocketmq.producer.endpoints=localhost:8081 +rocketmq.producer.topic=normalTopic +demo.rocketmq.fifo-topic=fifoTopic +demo.rocketmq.delay-topic=delayTopic +demo.rocketmq.trans-topic=transTopic +demo.rocketmq.normal-topic=normalTopic +demo.rocketmq.message-group=group1 \ No newline at end of file diff --git a/rocketmq-v5-client-spring-boot-samples/style/copyright/Apache.xml b/rocketmq-v5-client-spring-boot-samples/style/copyright/Apache.xml new file mode 100644 index 00000000..e3e3dec3 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/style/copyright/Apache.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/rocketmq-v5-client-spring-boot-samples/style/copyright/profiles_settings.xml b/rocketmq-v5-client-spring-boot-samples/style/copyright/profiles_settings.xml new file mode 100644 index 00000000..747c7e2b --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/style/copyright/profiles_settings.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/rocketmq-v5-client-spring-boot-samples/style/rmq_checkstyle.xml b/rocketmq-v5-client-spring-boot-samples/style/rmq_checkstyle.xml new file mode 100644 index 00000000..2e9658f4 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/style/rmq_checkstyle.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/rocketmq-v5-client-spring-boot-samples/style/rmq_codeStyle.xml b/rocketmq-v5-client-spring-boot-samples/style/rmq_codeStyle.xml new file mode 100644 index 00000000..9db075e3 --- /dev/null +++ b/rocketmq-v5-client-spring-boot-samples/style/rmq_codeStyle.xml @@ -0,0 +1,157 @@ + + + + + + + \ No newline at end of file diff --git a/rocketmq-v5-client-spring-boot-starter/pom.xml b/rocketmq-v5-client-spring-boot-starter/pom.xml new file mode 100644 index 00000000..a8d638bf --- /dev/null +++ b/rocketmq-v5-client-spring-boot-starter/pom.xml @@ -0,0 +1,55 @@ + + + + 4.0.0 + + org.apache.rocketmq + rocketmq-v5-client-spring-boot-parent + 2.2.4-SNAPSHOT + ../rocketmq-v5-client-spring-boot-parent/pom.xml + + + rocketmq-v5-client-spring-boot-starter + jar + 2.2.4-SNAPSHOT + + rocketmq-v5-client-spring-boot-starter + rocketmq-v5-client-spring-boot-starter + + + 8 + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-validation + + + org.apache.rocketmq + rocketmq-v5-client-spring-boot + + + + + diff --git a/rocketmq-v5-client-spring-boot/pom.xml b/rocketmq-v5-client-spring-boot/pom.xml new file mode 100644 index 00000000..960189a6 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/pom.xml @@ -0,0 +1,100 @@ + + + + 4.0.0 + + org.apache.rocketmq + rocketmq-v5-client-spring-boot-parent + 2.2.4-SNAPSHOT + ../rocketmq-v5-client-spring-boot-parent/pom.xml + + + rocketmq-v5-client-spring-boot + jar + + rocketmq-v5-client-spring-boot + rocketmq-v5-client-spring-boot + + + + org.slf4j + slf4j-api + + + org.apache.rocketmq + rocketmq-client-java + + + org.springframework.boot + spring-boot + true + + + org.springframework.boot + spring-boot-autoconfigure + true + + + org.springframework.boot + spring-boot-autoconfigure-processor + true + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework + spring-messaging + + + org.springframework + spring-core + + + org.springframework + spring-context + + + org.springframework + spring-aop + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + junit + junit + test + + + + + diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/ExtConsumerResetConfiguration.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/ExtConsumerResetConfiguration.java new file mode 100644 index 00000000..8615e2b6 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/ExtConsumerResetConfiguration.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.annotation; + +import org.springframework.stereotype.Component; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.Documented; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface ExtConsumerResetConfiguration { + + String ACCESS_KEY_PLACEHOLDER = "${rocketmq.simple-consumer.accessKey:}"; + String SECRET_KEY_PLACEHOLDER = "${rocketmq.simple-consumer.secretKey:}"; + String TAG_PLACEHOLDER = "${rocketmq.simple-consumer.tag:}"; + String TOPIC_PLACEHOLDER = "${rocketmq.simple-consumer.topic:}"; + String ENDPOINTS_PLACEHOLDER = "${rocketmq.simple-consumer.endpoints:}"; + String CONSUMER_GROUP_PLACEHOLDER = "${rocketmq.simple-consumer.consumerGroup:}"; + String FILTER_EXPRESSION_TYPE_PLACEHOLDER = "${rocketmq.simple-consumer.filterExpressionType:}"; + + /** + * The component name of the Consumer configuration. + */ + String value() default ""; + + /** + * The property of "access-key". + */ + String accessKey() default ACCESS_KEY_PLACEHOLDER; + + /** + * The property of "secret-key". + */ + String secretKey() default SECRET_KEY_PLACEHOLDER; + + /** + * Tag of consumer. + */ + String tag() default TAG_PLACEHOLDER; + + /** + * Topic name of consumer. + */ + String topic() default TOPIC_PLACEHOLDER; + + /** + * The access point that the SDK should communicate with. + */ + String endpoints() default ENDPOINTS_PLACEHOLDER; + + /** + * The load balancing group for the simple consumer. + */ + String consumerGroup() default CONSUMER_GROUP_PLACEHOLDER; + + /** + * The type of filter expression + */ + String filterExpressionType() default FILTER_EXPRESSION_TYPE_PLACEHOLDER; + + /** + * The requestTimeout of client,it is 3s by default. + */ + int requestTimeout() default 3; + + /** + * The max await time when receive messages from the server. + */ + int awaitDuration() default 0; + +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/ExtProducerResetConfiguration.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/ExtProducerResetConfiguration.java new file mode 100644 index 00000000..8849d0fc --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/ExtProducerResetConfiguration.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.annotation; + +import org.springframework.stereotype.Component; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.Documented; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface ExtProducerResetConfiguration { + + String ACCESS_KEY_PLACEHOLDER = "${rocketmq.producer.accessKey:}"; + String SECRET_KEY_PLACEHOLDER = "${rocketmq.producer.secretKey:}"; + String TOPIC_PLACEHOLDER = "${rocketmq.producer.topic:}"; + String ENDPOINTS_PLACEHOLDER = "${rocketmq.producer.endpoints:}"; + + /** + * The component name of the Producer configuration. + */ + String value() default ""; + + /** + * The property of "access-key". + */ + String accessKey() default ACCESS_KEY_PLACEHOLDER; + + /** + * The property of "secret-key". + */ + String secretKey() default SECRET_KEY_PLACEHOLDER; + + /** + * The access point that the SDK should communicate with. + */ + String endpoints() default ENDPOINTS_PLACEHOLDER; + + /** + * Topic name of consumer. + */ + String topic() default TOPIC_PLACEHOLDER; + + /** + * Request timeout is 3s by default. + */ + int requestTimeout() default 3; + + /** + * Enable or disable the use of Secure Sockets Layer (SSL) for network transport. + */ + boolean sslEnabled() default true; + + /** + * Max attempts for max internal retries of message publishing. + */ + int maxAttempts() default 3; + +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/RocketMQMessageListener.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/RocketMQMessageListener.java new file mode 100644 index 00000000..89107aa1 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/RocketMQMessageListener.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.Documented; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RocketMQMessageListener { + + String ACCESS_KEY_PLACEHOLDER = "${rocketmq.push-consumer.access-key:}"; + String SECRET_KEY_PLACEHOLDER = "${rocketmq.push-consumer.secret-key:}"; + String ENDPOINTS_PLACEHOLDER = "${rocketmq.push-consumer.endpoints:}"; + String TOPIC_PLACEHOLDER = "${rocketmq.push-consumer.endpoints:}"; + String TAG_PLACEHOLDER = "${rocketmq.push-consumer.tag:}"; + + /** + * The property of "access-key". + */ + String accessKey() default ACCESS_KEY_PLACEHOLDER; + + /** + * The property of "secret-key". + */ + String secretKey() default SECRET_KEY_PLACEHOLDER; + + /** + * The access point that the SDK should communicate with. + */ + String endpoints() default ENDPOINTS_PLACEHOLDER; + + /** + * Topic name of consumer. + */ + String topic() default TOPIC_PLACEHOLDER; + + /** + * Tag of consumer. + */ + String tag() default TAG_PLACEHOLDER; + + /** + * The type of filter expression + */ + String filterExpressionType() default "tag"; + + /** + * The load balancing group for the simple consumer. + */ + String consumerGroup() default ""; + + /** + * The requestTimeout of client,it is 3s by default. + */ + int requestTimeout() default 3; + + + int maxCachedMessageCount() default 1024; + + + int maxCacheMessageSizeInBytes() default 67108864; + + + int consumptionThreadCount() default 20; + + +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/RocketMQMessageListenerBeanPostProcessor.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/RocketMQMessageListenerBeanPostProcessor.java new file mode 100644 index 00000000..61f3e1d2 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/RocketMQMessageListenerBeanPostProcessor.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.annotation; + +import org.apache.rocketmq.client.autoconfigure.ListenerContainerConfiguration; +import org.springframework.aop.support.AopUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.core.OrderComparator; +import org.springframework.core.annotation.AnnotationUtils; + +import java.lang.reflect.AnnotatedElement; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +public class RocketMQMessageListenerBeanPostProcessor implements ApplicationContextAware, BeanPostProcessor, InitializingBean { + + private ApplicationContext applicationContext; + + private AnnotationEnhancer enhancer; + + private ListenerContainerConfiguration listenerContainerConfiguration; + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + Class targetClass = AopUtils.getTargetClass(bean); + RocketMQMessageListener ann = targetClass.getAnnotation(RocketMQMessageListener.class); + if (ann != null) { + RocketMQMessageListener enhance = enhance(targetClass, ann); + if (listenerContainerConfiguration != null) { + listenerContainerConfiguration.registerContainer(beanName, bean, enhance); + } + } + return bean; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public void afterPropertiesSet() throws Exception { + buildEnhancer(); + this.listenerContainerConfiguration = this.applicationContext.getBean(ListenerContainerConfiguration.class); + } + + private void buildEnhancer() { + if (this.applicationContext != null) { + Map enhancersMap = + this.applicationContext.getBeansOfType(AnnotationEnhancer.class, false, false); + if (enhancersMap.size() > 0) { + List enhancers = enhancersMap.values() + .stream() + .sorted(new OrderComparator()) + .collect(Collectors.toList()); + this.enhancer = (attrs, element) -> { + Map newAttrs = attrs; + for (AnnotationEnhancer enh : enhancers) { + newAttrs = enh.apply(newAttrs, element); + } + return attrs; + }; + } + } + } + + private RocketMQMessageListener enhance(AnnotatedElement element, RocketMQMessageListener ann) { + if (this.enhancer == null) { + return ann; + } else { + return AnnotationUtils.synthesizeAnnotation( + this.enhancer.apply(AnnotationUtils.getAnnotationAttributes(ann), element), RocketMQMessageListener.class, null); + } + } + + public interface AnnotationEnhancer extends BiFunction, AnnotatedElement, Map> { + } +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/RocketMQTransactionListener.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/RocketMQTransactionListener.java new file mode 100644 index 00000000..8d85f1d1 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/annotation/RocketMQTransactionListener.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.annotation; + +import org.springframework.stereotype.Component; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.Documented; + +@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface RocketMQTransactionListener { + String rocketMQTemplateBeanName() default "rocketMQClientTemplate"; +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/ExtConsumerResetConfiguration.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/ExtConsumerResetConfiguration.java new file mode 100644 index 00000000..3545dae0 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/ExtConsumerResetConfiguration.java @@ -0,0 +1,151 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.autoconfigure; + +import org.apache.rocketmq.client.support.RocketMQMessageConverter; +import org.apache.rocketmq.client.support.RocketMQUtil; +import org.apache.rocketmq.client.apis.ClientConfiguration; +import org.apache.rocketmq.client.apis.ClientServiceProvider; +import org.apache.rocketmq.client.apis.consumer.FilterExpression; +import org.apache.rocketmq.client.apis.consumer.SimpleConsumer; +import org.apache.rocketmq.client.apis.consumer.SimpleConsumerBuilder; + +import org.apache.rocketmq.client.core.RocketMQClientTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.support.BeanDefinitionValidationException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.time.Duration; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + + +@Configuration +public class ExtConsumerResetConfiguration implements ApplicationContextAware, SmartInitializingSingleton { + private static final Logger log = LoggerFactory.getLogger(ExtConsumerResetConfiguration.class); + + private ConfigurableApplicationContext applicationContext; + + private ConfigurableEnvironment environment; + + private RocketMQProperties rocketMQProperties; + + private RocketMQMessageConverter rocketMQMessageConverter; + + public ExtConsumerResetConfiguration(RocketMQMessageConverter rocketMQMessageConverter, + ConfigurableEnvironment environment, RocketMQProperties rocketMQProperties) { + this.rocketMQMessageConverter = rocketMQMessageConverter; + this.environment = environment; + this.rocketMQProperties = rocketMQProperties; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = (ConfigurableApplicationContext) applicationContext; + } + + @Override + public void afterSingletonsInstantiated() { + Map beans = this.applicationContext + .getBeansWithAnnotation(org.apache.rocketmq.client.annotation.ExtConsumerResetConfiguration.class) + .entrySet().stream().filter(entry -> !ScopedProxyUtils.isScopedTarget(entry.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + beans.forEach(this::registerTemplate); + } + + private void registerTemplate(String beanName, Object bean) { + Class clazz = AopProxyUtils.ultimateTargetClass(bean); + + if (!RocketMQClientTemplate.class.isAssignableFrom(bean.getClass())) { + throw new IllegalStateException(clazz + " is not instance of " + RocketMQClientTemplate.class.getName()); + } + org.apache.rocketmq.client.annotation.ExtConsumerResetConfiguration annotation = clazz.getAnnotation(org.apache.rocketmq.client.annotation.ExtConsumerResetConfiguration.class); + GenericApplicationContext genericApplicationContext = (GenericApplicationContext) applicationContext; + validate(annotation, genericApplicationContext); + + SimpleConsumerBuilder consumerBuilder = null; + SimpleConsumer simpleConsumer = null; + try { + consumerBuilder = createConsumer(annotation); + simpleConsumer = consumerBuilder.build(); + } catch (Exception e) { + log.error("Failed to startup SimpleConsumer for RocketMQTemplate {}", beanName, e); + } + RocketMQClientTemplate rocketMQTemplate = (RocketMQClientTemplate) bean; + rocketMQTemplate.setSimpleConsumerBuilder(consumerBuilder); + rocketMQTemplate.setSimpleConsumer(simpleConsumer); + rocketMQTemplate.setMessageConverter(rocketMQMessageConverter.getMessageConverter()); + log.info("Set real simpleConsumer to :{} {}", beanName, annotation.value()); + } + + private SimpleConsumerBuilder createConsumer(org.apache.rocketmq.client.annotation.ExtConsumerResetConfiguration annotation) { + RocketMQProperties.SimpleConsumer simpleConsumer = rocketMQProperties.getSimpleConsumer(); + String consumerGroupName = resolvePlaceholders(annotation.consumerGroup(), simpleConsumer.getConsumerGroup()); + String topicName = resolvePlaceholders(annotation.topic(), simpleConsumer.getTopic()); + String accessKey = resolvePlaceholders(annotation.accessKey(), simpleConsumer.getAccessKey()); + String secretKey = resolvePlaceholders(annotation.secretKey(), simpleConsumer.getSecretKey()); + String endPoints = resolvePlaceholders(annotation.endpoints(), simpleConsumer.getEndpoints()); + String tag = resolvePlaceholders(annotation.tag(), simpleConsumer.getTag()); + String filterExpressionType = resolvePlaceholders(annotation.filterExpressionType(), simpleConsumer.getFilterExpressionType()); + Duration requestTimeout = Duration.ofDays(annotation.requestTimeout()); + int awaitDuration = annotation.awaitDuration(); + Assert.hasText(topicName, "[topic] must not be null"); + ClientConfiguration clientConfiguration = RocketMQUtil.createClientConfiguration(accessKey, secretKey, endPoints, requestTimeout); + final ClientServiceProvider provider = ClientServiceProvider.loadService(); + FilterExpression filterExpression = RocketMQUtil.createFilterExpression(tag, filterExpressionType); + Duration duration = Duration.ofSeconds(awaitDuration); + SimpleConsumerBuilder simpleConsumerBuilder = provider.newSimpleConsumerBuilder(); + simpleConsumerBuilder.setClientConfiguration(clientConfiguration); + if (StringUtils.hasLength(consumerGroupName)) { + simpleConsumerBuilder.setConsumerGroup(consumerGroupName); + } + simpleConsumerBuilder.setAwaitDuration(duration); + if (Objects.nonNull(filterExpression)) { + simpleConsumerBuilder.setSubscriptionExpressions(Collections.singletonMap(topicName, filterExpression)); + } + return simpleConsumerBuilder; + } + + private String resolvePlaceholders(String text, String defaultValue) { + String value = environment.resolvePlaceholders(text); + return StringUtils.hasLength(value) ? value : defaultValue; + } + + private void validate(org.apache.rocketmq.client.annotation.ExtConsumerResetConfiguration annotation, + GenericApplicationContext genericApplicationContext) { + if (genericApplicationContext.isBeanNameInUse(annotation.value())) { + throw new BeanDefinitionValidationException( + String.format("Bean {} has been used in Spring Application Context, " + + "please check the @ExtRocketMQConsumerConfiguration", + annotation.value())); + } + } +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/ExtTemplateResetConfiguration.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/ExtTemplateResetConfiguration.java new file mode 100644 index 00000000..75bd4495 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/ExtTemplateResetConfiguration.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.autoconfigure; + +import org.apache.rocketmq.client.annotation.ExtProducerResetConfiguration; +import org.apache.rocketmq.client.support.RocketMQMessageConverter; +import org.apache.rocketmq.client.support.RocketMQUtil; +import org.apache.rocketmq.client.apis.ClientConfiguration; +import org.apache.rocketmq.client.apis.ClientServiceProvider; +import org.apache.rocketmq.client.apis.producer.ProducerBuilder; + +import org.apache.rocketmq.client.core.RocketMQClientTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.beans.factory.support.BeanDefinitionValidationException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.util.StringUtils; + +import java.time.Duration; +import java.util.Map; +import java.util.stream.Collectors; + +@Configuration +public class ExtTemplateResetConfiguration implements ApplicationContextAware, SmartInitializingSingleton { + + private static final Logger log = LoggerFactory.getLogger(ExtTemplateResetConfiguration.class); + + private ConfigurableApplicationContext applicationContext; + + private ConfigurableEnvironment environment; + + private RocketMQProperties rocketMQProperties; + + private RocketMQMessageConverter rocketMQMessageConverter; + + public ExtTemplateResetConfiguration(RocketMQMessageConverter rocketMQMessageConverter, + ConfigurableEnvironment environment, RocketMQProperties rocketMQProperties) { + this.rocketMQMessageConverter = rocketMQMessageConverter; + this.environment = environment; + this.rocketMQProperties = rocketMQProperties; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = (ConfigurableApplicationContext) applicationContext; + } + + + + + @Override + public void afterSingletonsInstantiated() { + Map beans = this.applicationContext.getBeansWithAnnotation(ExtProducerResetConfiguration.class) + .entrySet().stream().filter(entry -> !ScopedProxyUtils.isScopedTarget(entry.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + beans.forEach(this::registerTemplate); + } + + private void registerTemplate(String beanName, Object bean) { + Class clazz = AopProxyUtils.ultimateTargetClass(bean); + + if (!RocketMQClientTemplate.class.isAssignableFrom(bean.getClass())) { + throw new IllegalStateException(clazz + " is not instance of " + RocketMQClientTemplate.class.getName()); + } + + ExtProducerResetConfiguration annotation = clazz.getAnnotation(ExtProducerResetConfiguration.class); + GenericApplicationContext genericApplicationContext = (GenericApplicationContext) applicationContext; + validate(annotation, genericApplicationContext); + + ProducerBuilder producerBuilder = createProducer(annotation); + RocketMQClientTemplate rocketMQTemplate = (RocketMQClientTemplate) bean; + rocketMQTemplate.setProducerBuilder(producerBuilder); + rocketMQTemplate.setMessageConverter(rocketMQMessageConverter.getMessageConverter()); + log.info("Set real producerBuilder to :{} {}", beanName, annotation.value()); + } + + private ProducerBuilder createProducer(ExtProducerResetConfiguration annotation) { + RocketMQProperties.Producer producerConfig = rocketMQProperties.getProducer(); + if (producerConfig == null) { + producerConfig = new RocketMQProperties.Producer(); + } + String topic = environment.resolvePlaceholders(annotation.topic()); + topic = StringUtils.hasLength(topic) ? topic : producerConfig.getTopic(); + String endpoints = environment.resolvePlaceholders(annotation.endpoints()); + endpoints = StringUtils.hasLength(endpoints) ? endpoints : producerConfig.getEndpoints(); + String accessKey = environment.resolvePlaceholders(annotation.accessKey()); + accessKey = StringUtils.hasLength(accessKey) ? accessKey : producerConfig.getAccessKey(); + String secretKey = environment.resolvePlaceholders(annotation.secretKey()); + secretKey = StringUtils.hasLength(secretKey) ? secretKey : producerConfig.getSecretKey(); + int requestTimeout = annotation.requestTimeout(); + ClientConfiguration clientConfiguration = RocketMQUtil.createClientConfiguration(accessKey, secretKey, endpoints, Duration.ofDays(requestTimeout)); + final ClientServiceProvider provider = ClientServiceProvider.loadService(); + ProducerBuilder producerBuilder = provider.newProducerBuilder() + .setClientConfiguration(clientConfiguration).setMaxAttempts(annotation.maxAttempts()) + .setTopics(topic); + return producerBuilder; + } + + private void validate(ExtProducerResetConfiguration annotation, + GenericApplicationContext genericApplicationContext) { + if (genericApplicationContext.isBeanNameInUse(annotation.value())) { + throw new BeanDefinitionValidationException(String.format("Bean {} has been used in Spring Application Context, " + + "please check the @ExtTemplateConfiguration", + annotation.value())); + } + } + +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/ListenerContainerConfiguration.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/ListenerContainerConfiguration.java new file mode 100644 index 00000000..a6f46402 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/ListenerContainerConfiguration.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.autoconfigure; + +import org.apache.rocketmq.client.annotation.RocketMQMessageListener; +import org.apache.rocketmq.client.core.RocketMQListener; +import org.apache.rocketmq.client.support.DefaultListenerContainer; +import org.apache.rocketmq.client.support.RocketMQMessageConverter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.util.Assert; + +import java.time.Duration; +import java.util.concurrent.atomic.AtomicLong; + +@Configuration +public class ListenerContainerConfiguration implements ApplicationContextAware { + private final static Logger log = LoggerFactory.getLogger(ListenerContainerConfiguration.class); + + private ConfigurableApplicationContext applicationContext; + + private AtomicLong counter = new AtomicLong(0); + + private ConfigurableEnvironment environment; + + private RocketMQProperties rocketMQProperties; + + private RocketMQMessageConverter rocketMQMessageConverter; + + public ListenerContainerConfiguration(RocketMQMessageConverter rocketMQMessageConverter, + ConfigurableEnvironment environment, RocketMQProperties rocketMQProperties) { + this.rocketMQMessageConverter = rocketMQMessageConverter; + this.environment = environment; + this.rocketMQProperties = rocketMQProperties; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = (ConfigurableApplicationContext) applicationContext; + } + + public void registerContainer(String beanName, Object bean, RocketMQMessageListener annotation) { + validate(annotation); + String containerBeanName = String.format("%s_%s", DefaultListenerContainer.class.getName(), + counter.incrementAndGet()); + GenericApplicationContext genericApplicationContext = (GenericApplicationContext) applicationContext; + genericApplicationContext.registerBean(containerBeanName, DefaultListenerContainer.class, () -> createRocketMQListenerContainer(containerBeanName, bean, annotation)); + DefaultListenerContainer container = genericApplicationContext.getBean(containerBeanName, + DefaultListenerContainer.class); + if (!container.isRunning()) { + try { + container.start(); + } catch (Exception e) { + log.error("Started container failed. {}", container, e); + throw new RuntimeException(e); + } + } + log.info("Register the listener to container, listenerBeanName:{}, containerBeanName:{}", beanName, containerBeanName); + } + + private DefaultListenerContainer createRocketMQListenerContainer(String name, Object bean, RocketMQMessageListener annotation) { + DefaultListenerContainer container = new DefaultListenerContainer(); + container.setName(name); + container.setRocketMQMessageListener(annotation); + container.setMessageListener((RocketMQListener) bean); + container.setAccessKey(environment.resolvePlaceholders(annotation.accessKey())); + container.setSecretKey(environment.resolvePlaceholders(annotation.secretKey())); + container.setConsumerGroup(environment.resolvePlaceholders(annotation.consumerGroup())); + container.setTag(environment.resolvePlaceholders(annotation.tag())); + container.setEndpoints(environment.resolvePlaceholders(annotation.endpoints())); + container.setTopic(environment.resolvePlaceholders(annotation.topic())); + container.setRequestTimeout(Duration.ofDays(annotation.requestTimeout())); + container.setMaxCachedMessageCount(annotation.maxCachedMessageCount()); + container.setConsumptionThreadCount(annotation.consumptionThreadCount()); + container.setMaxCacheMessageSizeInBytes(annotation.maxCacheMessageSizeInBytes()); + container.setType(annotation.filterExpressionType()); + return container; + } + + private void validate(RocketMQMessageListener annotation) { + Assert.hasText(annotation.accessKey(), "[accessKey] must not be null"); + Assert.hasText(annotation.secretKey(), "[secretKey] must not be null"); + Assert.hasText(annotation.topic(), "[topic] must not be null"); + Assert.hasText(annotation.endpoints(), "[endpoints] must not be null"); + } +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/MessageConverterConfiguration.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/MessageConverterConfiguration.java new file mode 100644 index 00000000..25e5512c --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/MessageConverterConfiguration.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.autoconfigure; + + +import org.apache.rocketmq.client.support.RocketMQMessageConverter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @see RocketMQMessageConverter + */ +@Configuration +@ConditionalOnMissingBean(RocketMQMessageConverter.class) +class MessageConverterConfiguration { + + @Bean + public RocketMQMessageConverter createMessageConverter() { + return new RocketMQMessageConverter(); + } + +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQAutoConfiguration.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQAutoConfiguration.java new file mode 100644 index 00000000..11763c88 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQAutoConfiguration.java @@ -0,0 +1,156 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.autoconfigure; + +import org.apache.rocketmq.client.support.RocketMQMessageConverter; +import org.apache.rocketmq.client.support.RocketMQUtil; +import org.apache.rocketmq.client.apis.ClientConfiguration; +import org.apache.rocketmq.client.apis.ClientServiceProvider; +import org.apache.rocketmq.client.apis.consumer.FilterExpression; +import org.apache.rocketmq.client.apis.consumer.SimpleConsumerBuilder; +import org.apache.rocketmq.client.apis.producer.ProducerBuilder; +import org.apache.rocketmq.client.core.RocketMQClientTemplate; +import org.apache.rocketmq.client.java.impl.producer.ProducerBuilderImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.time.Duration; +import java.util.Collections; +import java.util.Objects; + +@Configuration +@EnableConfigurationProperties(RocketMQProperties.class) +@Import({MessageConverterConfiguration.class, ListenerContainerConfiguration.class, ExtTemplateResetConfiguration.class, + ExtConsumerResetConfiguration.class, RocketMQTransactionConfiguration.class, RocketMQListenerConfiguration.class}) +@AutoConfigureAfter({MessageConverterConfiguration.class}) +@AutoConfigureBefore({RocketMQTransactionConfiguration.class}) +public class RocketMQAutoConfiguration implements ApplicationContextAware { + private static final Logger log = LoggerFactory.getLogger(RocketMQAutoConfiguration.class); + public static final String ROCKETMQ_TEMPLATE_DEFAULT_GLOBAL_NAME = "rocketMQClientTemplate"; + public static final String PRODUCER_BUILDER_BEAN_NAME = "producerBuilder"; + public static final String SIMPLE_CONSUMER_BUILDER_BEAN_NAME = "simpleConsumerBuilder"; + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + /** + * description:gRPC-SDK ProducerBuilder + */ + @Bean(PRODUCER_BUILDER_BEAN_NAME) + @ConditionalOnMissingBean(ProducerBuilderImpl.class) + @ConditionalOnProperty(prefix = "rocketmq", value = {"producer.endpoints"}) + public ProducerBuilder producerBuilder(RocketMQProperties rocketMQProperties) { + RocketMQProperties.Producer rocketMQProducer = rocketMQProperties.getProducer(); + log.info("Init Producer Args: " + rocketMQProducer); + String topic = rocketMQProducer.getTopic(); + String endPoints = rocketMQProducer.getEndpoints(); + Assert.hasText(endPoints, "[rocketmq.producer.endpoints] must not be null"); + ClientConfiguration clientConfiguration = RocketMQUtil.createProducerClientConfiguration(rocketMQProducer); + final ClientServiceProvider provider = ClientServiceProvider.loadService(); + ProducerBuilder producerBuilder; + producerBuilder = provider.newProducerBuilder() + .setClientConfiguration(clientConfiguration) + // Set the topic name(s), which is optional but recommended. It makes producer could prefetch the topic + // route before message publishing. + .setTopics(rocketMQProducer.getTopic()) + .setMaxAttempts(rocketMQProducer.getMaxAttempts()); + log.info(String.format("a producer init on proxy %s", endPoints)); + return producerBuilder; + } + + + /** + * description:gRPC-SDK SimpleConsumerBuilder + */ + @Bean(SIMPLE_CONSUMER_BUILDER_BEAN_NAME) + @ConditionalOnMissingBean(SimpleConsumerBuilder.class) + @ConditionalOnProperty(prefix = "rocketmq", value = {"simple-consumer.endpoints"}) + public SimpleConsumerBuilder simpleConsumerBuilder(RocketMQProperties rocketMQProperties) { + RocketMQProperties.SimpleConsumer simpleConsumer = rocketMQProperties.getSimpleConsumer(); + final ClientServiceProvider provider = ClientServiceProvider.loadService(); + String consumerGroup = simpleConsumer.getConsumerGroup(); + FilterExpression filterExpression = RocketMQUtil.createFilterExpression(simpleConsumer.getTag(), simpleConsumer.getFilterExpressionType()); + ClientConfiguration clientConfiguration = RocketMQUtil.createConsumerClientConfiguration(simpleConsumer); + SimpleConsumerBuilder simpleConsumerBuilder = provider.newSimpleConsumerBuilder() + .setClientConfiguration(clientConfiguration); + // set await duration for long-polling. + simpleConsumerBuilder.setAwaitDuration(Duration.ofSeconds(simpleConsumer.getAwaitDuration())); + + // Set the consumer group name. + if (StringUtils.hasLength(consumerGroup)) { + simpleConsumerBuilder.setConsumerGroup(consumerGroup); + } + // Set the subscription for the consumer. + if (Objects.nonNull(filterExpression)) { + simpleConsumerBuilder.setSubscriptionExpressions(Collections.singletonMap(simpleConsumer.getTopic(), filterExpression)); + } + return simpleConsumerBuilder; + } + + @Bean(destroyMethod = "destroy") + @Conditional(ProducerOrConsumerPropertyCondition.class) + @ConditionalOnMissingBean(name = ROCKETMQ_TEMPLATE_DEFAULT_GLOBAL_NAME) + public RocketMQClientTemplate rocketMQClientTemplate(RocketMQMessageConverter rocketMQMessageConverter) { + RocketMQClientTemplate rocketMQClientTemplate = new RocketMQClientTemplate(); + + if (applicationContext.containsBean(PRODUCER_BUILDER_BEAN_NAME)) { + rocketMQClientTemplate.setProducerBuilder((ProducerBuilder) applicationContext.getBean(PRODUCER_BUILDER_BEAN_NAME)); + } + if (applicationContext.containsBean(SIMPLE_CONSUMER_BUILDER_BEAN_NAME)) { + rocketMQClientTemplate.setSimpleConsumerBuilder((SimpleConsumerBuilder) applicationContext.getBean(SIMPLE_CONSUMER_BUILDER_BEAN_NAME)); + } + rocketMQClientTemplate.setMessageConverter(rocketMQMessageConverter.getMessageConverter()); + return rocketMQClientTemplate; + } + + /** + * + */ + static class ProducerOrConsumerPropertyCondition extends AnyNestedCondition { + + public ProducerOrConsumerPropertyCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnBean(ProducerBuilder.class) + static class DefaultMQProducerExistsCondition { + } + + @ConditionalOnBean(SimpleConsumerBuilder.class) + static class SimpleConsumerExistsCondition { + } + } +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQListenerConfiguration.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQListenerConfiguration.java new file mode 100644 index 00000000..c8afc127 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQListenerConfiguration.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.autoconfigure; + +import org.apache.rocketmq.client.annotation.RocketMQMessageListenerBeanPostProcessor; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.RootBeanDefinition; +import org.springframework.boot.autoconfigure.AutoConfigureAfter; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; +import org.springframework.core.type.AnnotationMetadata; + +@Configuration +@AutoConfigureAfter(RocketMQAutoConfiguration.class) +public class RocketMQListenerConfiguration implements ImportBeanDefinitionRegistrar { + @Override + public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (!registry.containsBeanDefinition(RocketMQMessageListenerBeanPostProcessor.class.getName())) { + registry.registerBeanDefinition(RocketMQMessageListenerBeanPostProcessor.class.getName(), + new RootBeanDefinition(RocketMQMessageListenerBeanPostProcessor.class)); + } + } +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQProperties.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQProperties.java new file mode 100644 index 00000000..64c9a6a1 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQProperties.java @@ -0,0 +1,300 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.autoconfigure; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@SuppressWarnings("WeakerAccess") +@ConfigurationProperties(prefix = "rocketmq") +public class RocketMQProperties { + + private Producer producer; + + private SimpleConsumer simpleConsumer = new SimpleConsumer(); + + public Producer getProducer() { + return producer; + } + + public void setProducer(Producer producer) { + this.producer = producer; + } + + public SimpleConsumer getSimpleConsumer() { + return simpleConsumer; + } + + public void setSimpleConsumer(SimpleConsumer simpleConsumer) { + this.simpleConsumer = simpleConsumer; + } + + public static class Producer { + + /** + * The property of "access-key". + */ + private String accessKey; + + /** + * The property of "secret-key". + */ + private String secretKey; + + /** + * The access point that the SDK should communicate with. + */ + private String endpoints; + + /** + * Topic is used to prefetch the route. + */ + private String topic; + + /** + * Request timeout is 3s by default. + */ + private int requestTimeout = 3; + + /** + * Enable or disable the use of Secure Sockets Layer (SSL) for network transport. + */ + private boolean sslEnabled = true; + + /** + * Max attempts for max internal retries of message publishing. + */ + private int maxAttempts = 3; + + public String getAccessKey() { + return accessKey; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public String getEndpoints() { + return endpoints; + } + + public void setEndpoints(String endpoints) { + this.endpoints = endpoints; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public int getRequestTimeout() { + return requestTimeout; + } + + public void setRequestTimeout(int requestTimeout) { + this.requestTimeout = requestTimeout; + } + + public boolean isSslEnabled() { + return sslEnabled; + } + + public void setSslEnabled(boolean sslEnabled) { + this.sslEnabled = sslEnabled; + } + + public int getMaxAttempts() { + return maxAttempts; + } + + public void setMaxAttempts(int maxAttempts) { + this.maxAttempts = maxAttempts; + } + + @Override + public String toString() { + return "Producer{" + + "accessKey='" + accessKey + '\'' + + ", secretKey='" + secretKey + '\'' + + ", endpoints='" + endpoints + '\'' + + ", topic='" + topic + '\'' + + ", requestTimeout=" + requestTimeout + + ", sslEnabled=" + sslEnabled + + '}'; + } + } + + public static class SimpleConsumer { + + /** + * The property of "access-key". + */ + private String accessKey; + + /** + * The property of "secret-key". + */ + private String secretKey; + + /** + * The access point that the SDK should communicate with. + */ + private String endpoints; + + /** + * The load balancing group for the simple consumer. + */ + private String consumerGroup; + + /** + * The max await time when receive messages from the server. + */ + private int awaitDuration = 0; + + /** + * Tag of consumer. + */ + private String tag; + + /** + * Topic name of consumer. + */ + private String topic; + + /** + * The requestTimeout of client,it is 3s by default. + */ + private int requestTimeout = 3; + + /** + * The type of filter expression + */ + private String filterExpressionType = "tag"; + + /** + * Enable or disable the use of Secure Sockets Layer (SSL) for network transport. + */ + private boolean sslEnabled = true; + + public String getAccessKey() { + return accessKey; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public String getEndpoints() { + return endpoints; + } + + public void setEndpoints(String endpoints) { + this.endpoints = endpoints; + } + + public String getConsumerGroup() { + return consumerGroup; + } + + public void setConsumerGroup(String consumerGroup) { + this.consumerGroup = consumerGroup; + } + + public int getAwaitDuration() { + return awaitDuration; + } + + public void setAwaitDuration(int awaitDuration) { + this.awaitDuration = awaitDuration; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public int getRequestTimeout() { + return requestTimeout; + } + + public void setRequestTimeout(int requestTimeout) { + this.requestTimeout = requestTimeout; + } + + public boolean isSslEnabled() { + return sslEnabled; + } + + public void setSslEnabled(boolean sslEnabled) { + this.sslEnabled = sslEnabled; + } + + public String getFilterExpressionType() { + return filterExpressionType; + } + + public void setFilterExpressionType(String filterExpressionType) { + this.filterExpressionType = filterExpressionType; + } + + @Override + public String toString() { + return "SimpleConsumer{" + + "accessKey='" + accessKey + '\'' + + ", secretKey='" + secretKey + '\'' + + ", endpoints='" + endpoints + '\'' + + ", consumerGroup='" + consumerGroup + '\'' + + ", awaitDuration='" + awaitDuration + '\'' + + ", tag='" + tag + '\'' + + ", topic='" + topic + '\'' + + ", requestTimeout=" + requestTimeout + + ", filterExpressionType='" + filterExpressionType + '\'' + + ", sslEnabled=" + sslEnabled + + '}'; + } + } + +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQTransactionConfiguration.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQTransactionConfiguration.java new file mode 100644 index 00000000..13b35a79 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/autoconfigure/RocketMQTransactionConfiguration.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.autoconfigure; + +import org.apache.rocketmq.client.annotation.RocketMQTransactionListener; +import org.apache.rocketmq.client.apis.producer.TransactionChecker; +import org.apache.rocketmq.client.core.RocketMQClientTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.framework.AopProxyUtils; +import org.springframework.aop.scope.ScopedProxyUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; + +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +@Configuration +public class RocketMQTransactionConfiguration implements ApplicationContextAware, SmartInitializingSingleton { + private final static Logger log = LoggerFactory.getLogger(RocketMQTransactionConfiguration.class); + + private ConfigurableApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = (ConfigurableApplicationContext) applicationContext; + } + + @Override + public void afterSingletonsInstantiated() { + Map beans = this.applicationContext.getBeansWithAnnotation(RocketMQTransactionListener.class) + .entrySet().stream().filter(entry -> !ScopedProxyUtils.isScopedTarget(entry.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + beans.forEach(this::handleTransactionChecker); + } + + public void handleTransactionChecker(String beanName, Object bean) { + Class clazz = AopProxyUtils.ultimateTargetClass(bean); + if (!TransactionChecker.class.isAssignableFrom(bean.getClass())) { + throw new IllegalStateException(clazz + " is not instance of " + TransactionChecker.class.getName()); + } + RocketMQTransactionListener annotation = clazz.getAnnotation(RocketMQTransactionListener.class); + if (Objects.isNull(annotation)) { + throw new IllegalStateException("The transactionListener annotation is missing"); + } + RocketMQClientTemplate rocketMQTemplate = (RocketMQClientTemplate) applicationContext.getBean(annotation.rocketMQTemplateBeanName()); + if ((rocketMQTemplate.getProducerBuilder()) != null) { + rocketMQTemplate.getProducerBuilder().setTransactionChecker((TransactionChecker) bean); + } + } + +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/common/Pair.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/common/Pair.java new file mode 100644 index 00000000..e9666e0a --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/common/Pair.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.common; + +public class Pair { + private T1 sendReceipt; + private T2 transaction; + + public Pair(T1 sendReceipt, T2 transaction) { + this.sendReceipt = sendReceipt; + this.transaction = transaction; + } + + public T1 getSendReceipt() { + return sendReceipt; + } + + public void setLeft(T1 sendReceipt) { + this.sendReceipt = sendReceipt; + } + + public T2 getTransaction() { + return transaction; + } + + public void setTransaction(T2 transaction) { + this.transaction = transaction; + } +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/core/RocketMQClientTemplate.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/core/RocketMQClientTemplate.java new file mode 100644 index 00000000..890c6f21 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/core/RocketMQClientTemplate.java @@ -0,0 +1,395 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.core; + +import org.apache.rocketmq.client.common.Pair; +import org.apache.rocketmq.client.support.RocketMQMessageConverter; +import org.apache.rocketmq.client.support.RocketMQUtil; +import org.apache.rocketmq.client.apis.ClientException; +import org.apache.rocketmq.client.apis.consumer.SimpleConsumer; +import org.apache.rocketmq.client.apis.consumer.SimpleConsumerBuilder; +import org.apache.rocketmq.client.apis.message.MessageView; +import org.apache.rocketmq.client.apis.producer.Producer; +import org.apache.rocketmq.client.apis.producer.ProducerBuilder; +import org.apache.rocketmq.client.apis.producer.SendReceipt; +import org.apache.rocketmq.client.apis.producer.Transaction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.core.AbstractMessageSendingTemplate; +import org.springframework.messaging.support.MessageBuilder; + +import java.io.IOException; +import java.time.Duration; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + + +@SuppressWarnings({"WeakerAccess", "unused"}) +public class RocketMQClientTemplate extends AbstractMessageSendingTemplate implements DisposableBean { + + private static final Logger log = LoggerFactory.getLogger(RocketMQClientTemplate.class); + + private ProducerBuilder producerBuilder; + + private SimpleConsumerBuilder simpleConsumerBuilder; + + private Producer producer; + + private SimpleConsumer simpleConsumer; + + private RocketMQMessageConverter rocketMQMessageConverter = new RocketMQMessageConverter(); + + private String charset = "UTF-8"; + + public Producer getProducer() { + if (Objects.isNull(producer)) { + try { + return producerBuilder.build(); + } catch (ClientException e) { + throw new RuntimeException(e); + } + } + return producer; + } + + public void setProducer(Producer producer) { + this.producer = producer; + } + + + public SimpleConsumer getSimpleConsumer() { + if (Objects.isNull(simpleConsumer)) { + try { + return simpleConsumerBuilder.build(); + } catch (ClientException e) { + throw new RuntimeException(e); + } + } + return simpleConsumer; + } + + public void setSimpleConsumer(SimpleConsumer simpleConsumer) { + this.simpleConsumer = simpleConsumer; + } + + public ProducerBuilder getProducerBuilder() { + return producerBuilder; + } + + public void setProducerBuilder(ProducerBuilder producerBuilder) { + this.producerBuilder = producerBuilder; + } + + public SimpleConsumerBuilder getSimpleConsumerBuilder() { + return simpleConsumerBuilder; + } + + public void setSimpleConsumerBuilder(SimpleConsumerBuilder simpleConsumerBuilder) { + this.simpleConsumerBuilder = simpleConsumerBuilder; + } + + public RocketMQMessageConverter getRocketMQMessageConverter() { + return rocketMQMessageConverter; + } + + public void setRocketMQMessageConverter(RocketMQMessageConverter rocketMQMessageConverter) { + this.rocketMQMessageConverter = rocketMQMessageConverter; + } + + public String getCharset() { + return charset; + } + + public void setCharset(String charset) { + this.charset = charset; + } + + + @Override + public void destroy() throws Exception { + if (Objects.nonNull(producer)) { + producer.close(); + } + if (Objects.nonNull(simpleConsumer)) { + simpleConsumer.close(); + } + } + + @Override + protected void doSend(String destination, Message message) { + SendReceipt sendReceipt = syncSendGrpcMessage(destination, message, null, null); + if (log.isDebugEnabled()) { + log.debug("send message to `{}` finished. result:{}", destination, sendReceipt); + } + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent + * @param messageDelayTime Time for message delay + * @return SendReceipt Synchronous Task Results + */ + public SendReceipt syncSendDelayMessage(String destination, Object payload, Duration messageDelayTime) { + Message message = MessageBuilder.withPayload(payload).build(); + return syncSendGrpcMessage(destination, message, messageDelayTime, null); + } + + public SendReceipt syncSendDelayMessage(String destination, String payload, Duration messageDelayTime) { + Message message = MessageBuilder.withPayload(payload).build(); + return syncSendGrpcMessage(destination, message, messageDelayTime, null); + } + + public SendReceipt syncSendDelayMessage(String destination, byte[] payload, Duration messageDelayTime) { + Message message = MessageBuilder.withPayload(payload).build(); + return syncSendGrpcMessage(destination, message, messageDelayTime, null); + } + + public SendReceipt syncSendDelayMessage(String destination, Message message, Duration messageDelayTime) { + return syncSendGrpcMessage(destination, message, messageDelayTime, null); + } + + public SendReceipt syncSendFifoMessage(String destination, Object payload, String messageGroup) { + Message message = MessageBuilder.withPayload(payload).build(); + return syncSendGrpcMessage(destination, message, null, messageGroup); + } + + public SendReceipt syncSendFifoMessage(String destination, String payload, String messageGroup) { + Message message = MessageBuilder.withPayload(payload).build(); + return syncSendGrpcMessage(destination, message, null, messageGroup); + } + + public SendReceipt syncSendFifoMessage(String destination, byte[] payload, String messageGroup) { + Message message = MessageBuilder.withPayload(payload).build(); + return syncSendGrpcMessage(destination, message, null, messageGroup); + } + + public SendReceipt syncSendFifoMessage(String destination, Message message, String messageGroup) { + return syncSendGrpcMessage(destination, message, null, messageGroup); + } + + public SendReceipt syncSendNormalMessage(String destination, Object payload) { + Message message = MessageBuilder.withPayload(payload).build(); + return syncSendGrpcMessage(destination, message, null, null); + } + + public SendReceipt syncSendNormalMessage(String destination, String payload) { + Message message = MessageBuilder.withPayload(payload).build(); + return syncSendGrpcMessage(destination, message, null, null); + } + + public SendReceipt syncSendNormalMessage(String destination, Message message) { + return syncSendGrpcMessage(destination, message, null, null); + } + + public SendReceipt syncSendNormalMessage(String destination, byte[] payload) { + Message message = MessageBuilder.withPayload(payload).build(); + return syncSendGrpcMessage(destination, message, null, null); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link Message} the message to be sent. + * @param messageDelayTime Time for message delay + * @param messageGroup message group name + * @return SendReceipt Synchronous Task Results + */ + public SendReceipt syncSendGrpcMessage(String destination, Message message, Duration messageDelayTime, String messageGroup) { + if (Objects.isNull(message) || Objects.isNull(message.getPayload())) { + log.error("send request message failed. destination:{}, message is null ", destination); + throw new IllegalArgumentException("`message` and `message.payload` cannot be null"); + } + SendReceipt sendReceipt = null; + try { + org.apache.rocketmq.client.apis.message.Message rocketMsg = this.createRocketMQMessage(destination, message, messageDelayTime, messageGroup); + Producer grpcProducer = this.getProducer(); + try { + sendReceipt = grpcProducer.send(rocketMsg); + log.info("Send message successfully, messageId={}", sendReceipt.getMessageId()); + } catch (Throwable t) { + log.error("Failed to send message", t); + } + } catch (Exception e) { + log.error("send request message failed. destination:{}, message:{} ", destination, message); + throw new MessagingException(e.getMessage(), e); + } + return sendReceipt; + } + + + public CompletableFuture asyncSendWithObjectPayload(String destination, Object payload, Duration messageDelayTime, String messageGroup, CompletableFuture future) { + Message message = MessageBuilder.withPayload(payload).build(); + return asyncSend(destination, message, messageDelayTime, messageGroup, future); + } + + public CompletableFuture asyncSendWithStringPayload(String destination, String payload, Duration messageDelayTime, String messageGroup, CompletableFuture future) { + Message message = MessageBuilder.withPayload(payload).build(); + return asyncSend(destination, message, messageDelayTime, messageGroup, future); + } + + public CompletableFuture asyncSendWithBytePayload(String destination, byte[] payload, Duration messageDelayTime, String messageGroup, CompletableFuture future) { + Message message = MessageBuilder.withPayload(payload).build(); + return asyncSend(destination, message, messageDelayTime, messageGroup, future); + } + + public CompletableFuture asyncSendWithMessagePayload(String destination, Message payload, Duration messageDelayTime, String messageGroup, CompletableFuture future) { + return asyncSend(destination, payload, messageDelayTime, messageGroup, future); + } + + public CompletableFuture asyncSendNormalMessage(String destination, Object payload, CompletableFuture future) { + return asyncSendWithObjectPayload(destination, payload, null, null, future); + } + + public CompletableFuture asyncSendNormalMessage(String destination, String payload, CompletableFuture future) { + return asyncSendWithStringPayload(destination, payload, null, null, future); + } + + public CompletableFuture asyncSendNormalMessage(String destination, byte[] payload, CompletableFuture future) { + return asyncSendWithBytePayload(destination, payload, null, null, future); + } + + public CompletableFuture asyncSendNormalMessage(String destination, Message payload, CompletableFuture future) { + return asyncSendWithMessagePayload(destination, payload, null, null, future); + } + + public CompletableFuture asyncSendFifoMessage(String destination, Object payload, String messageGroup, CompletableFuture future) { + return asyncSendWithObjectPayload(destination, payload, null, messageGroup, future); + } + + public CompletableFuture asyncSendFifoMessage(String destination, String payload, String messageGroup, CompletableFuture future) { + return asyncSendWithStringPayload(destination, payload, null, messageGroup, future); + } + + public CompletableFuture asyncSendFifoMessage(String destination, byte[] payload, String messageGroup, CompletableFuture future) { + return asyncSendWithBytePayload(destination, payload, null, messageGroup, future); + } + + public CompletableFuture asyncSendFifoMessage(String destination, Message payload, String messageGroup, CompletableFuture future) { + return asyncSendWithMessagePayload(destination, payload, null, messageGroup, future); + } + + public CompletableFuture asyncSendDelayMessage(String destination, Object payload, Duration messageDelayTime, CompletableFuture future) { + return asyncSendWithObjectPayload(destination, payload, messageDelayTime, null, future); + } + + public CompletableFuture asyncSendDelayMessage(String destination, String payload, Duration messageDelayTime, CompletableFuture future) { + return asyncSendWithStringPayload(destination, payload, messageDelayTime, null, future); + } + + public CompletableFuture asyncSendDelayMessage(String destination, byte[] payload, Duration messageDelayTime, CompletableFuture future) { + return asyncSendWithBytePayload(destination, payload, messageDelayTime, null, future); + } + + public CompletableFuture asyncSendDelayMessage(String destination, Message payload, Duration messageDelayTime, CompletableFuture future) { + return asyncSendWithMessagePayload(destination, payload, messageDelayTime, null, future); + } + + public CompletableFuture asyncSend(String destination, Message message, Duration messageDelayTime, String messageGroup, CompletableFuture future) { + if (Objects.isNull(message) || Objects.isNull(message.getPayload())) { + log.error("send request message failed. destination:{}, message is null ", destination); + throw new IllegalArgumentException("`message` and `message.payload` cannot be null"); + } + Producer grpcProducer = this.getProducer(); + try { + org.apache.rocketmq.client.apis.message.Message rocketMsg = this.createRocketMQMessage(destination, message, messageDelayTime, messageGroup); + future = grpcProducer.sendAsync(rocketMsg); + } catch (Exception e) { + log.error("send request message failed. destination:{}, message:{} ", destination, message); + throw new MessagingException(e.getMessage(), e); + } + return future; + } + + public Pair sendMessageInTransaction(String destination, Object payload) throws ClientException { + Message message = MessageBuilder.withPayload(payload).build(); + return sendTransactionMessage(destination, message); + } + + public Pair sendMessageInTransaction(String destination, String payload) throws ClientException { + Message message = MessageBuilder.withPayload(payload).build(); + return sendTransactionMessage(destination, message); + } + + public Pair sendMessageInTransaction(String destination, byte[] payload) throws ClientException { + Message message = MessageBuilder.withPayload(payload).build(); + return sendTransactionMessage(destination, message); + } + + + /** + * @param destination formats: `topicName:tags` + * @param message {@link Message} the message to be sent. + * @return CompletableFuture Asynchronous Task Results + */ + public Pair sendTransactionMessage(String destination, Message message) { + if (Objects.isNull(message) || Objects.isNull(message.getPayload())) { + log.error("send request message failed. destination:{}, message is null ", destination); + throw new IllegalArgumentException("`message` and `message.payload` cannot be null"); + } + final SendReceipt sendReceipt; + Producer grpcProducer = this.getProducer(); + org.apache.rocketmq.client.apis.message.Message rocketMsg = this.createRocketMQMessage(destination, message, null, null); + final Transaction transaction; + try { + transaction = grpcProducer.beginTransaction(); + sendReceipt = grpcProducer.send(rocketMsg, transaction); + log.info("Send transaction message successfully, messageId={}", sendReceipt.getMessageId()); + } catch (ClientException e) { + log.error("send request message failed. destination:{}, message:{} ", destination, message); + throw new RuntimeException(e); + } + return new Pair<>(sendReceipt, transaction); + } + + + public List receive(int maxMessageNum, Duration invisibleDuration) throws ClientException { + SimpleConsumer simpleConsumer = this.getSimpleConsumer(); + return simpleConsumer.receive(maxMessageNum, invisibleDuration); + } + + + public CompletableFuture> receiveAsync(int maxMessageNum, Duration invisibleDuration) throws ClientException, IOException { + SimpleConsumer simpleConsumer = this.getSimpleConsumer(); + CompletableFuture> listCompletableFuture = simpleConsumer.receiveAsync(maxMessageNum, invisibleDuration); + simpleConsumer.close(); + return listCompletableFuture; + } + + + public void ack(MessageView message) throws ClientException { + SimpleConsumer simpleConsumer = this.getSimpleConsumer(); + simpleConsumer.ack(message); + } + + + public CompletableFuture ackAsync(MessageView messageView) { + SimpleConsumer simpleConsumer = this.getSimpleConsumer(); + return simpleConsumer.ackAsync(messageView); + } + + + private org.apache.rocketmq.client.apis.message.Message createRocketMQMessage(String destination, Message message, Duration messageDelayTime, String messageGroup) { + Message msg = this.doConvert(message.getPayload(), message.getHeaders(), null); + return RocketMQUtil.convertToClientMessage(getMessageConverter(), charset, + destination, msg, messageDelayTime, messageGroup); + } + + +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/core/RocketMQListener.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/core/RocketMQListener.java new file mode 100644 index 00000000..91aa1d1a --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/core/RocketMQListener.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.core; + +import org.apache.rocketmq.client.apis.consumer.ConsumeResult; +import org.apache.rocketmq.client.apis.consumer.MessageListener; +import org.apache.rocketmq.client.apis.message.MessageView; + +public interface RocketMQListener extends MessageListener { + ConsumeResult consume(MessageView messageView); +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/core/RocketMQTransactionChecker.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/core/RocketMQTransactionChecker.java new file mode 100644 index 00000000..fc90cf93 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/core/RocketMQTransactionChecker.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.core; + +import org.apache.rocketmq.client.apis.message.MessageView; +import org.apache.rocketmq.client.apis.producer.TransactionChecker; +import org.apache.rocketmq.client.apis.producer.TransactionResolution; + +public interface RocketMQTransactionChecker extends TransactionChecker { + TransactionResolution check(MessageView var1); +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/DefaultListenerContainer.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/DefaultListenerContainer.java new file mode 100644 index 00000000..c2466c05 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/DefaultListenerContainer.java @@ -0,0 +1,348 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.support; + +import org.apache.rocketmq.client.annotation.RocketMQMessageListener; +import org.apache.rocketmq.client.apis.ClientConfiguration; +import org.apache.rocketmq.client.apis.ClientServiceProvider; +import org.apache.rocketmq.client.apis.consumer.FilterExpressionType; +import org.apache.rocketmq.client.apis.consumer.PushConsumer; +import org.apache.rocketmq.client.apis.consumer.PushConsumerBuilder; +import org.apache.rocketmq.client.apis.consumer.FilterExpression; +import org.apache.rocketmq.client.core.RocketMQListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.SmartLifecycle; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.io.IOException; +import java.time.Duration; +import java.util.Collections; +import java.util.Objects; + + +public class DefaultListenerContainer implements InitializingBean, + RocketMQListenerContainer, SmartLifecycle, ApplicationContextAware { + private final static Logger log = LoggerFactory.getLogger(DefaultListenerContainer.class); + + private ApplicationContext applicationContext; + + /** + * The name of the DefaultRocketMQListenerContainer instance + */ + private String name; + + private boolean running; + + private PushConsumer pushConsumer; + + private PushConsumerBuilder pushConsumerBuilder; + + private RocketMQListener rocketMQListener; + + private RocketMQMessageListener rocketMQMessageListener; + + String accessKey; + + String secretKey; + + String endpoints; + + String consumerGroup; + + String tag; + + String topic; + + String type; + + FilterExpressionType filterExpressionType; + + Duration requestTimeout; + + int maxCachedMessageCount = 1024; + + int maxCacheMessageSizeInBytes = 67108864; + + int consumptionThreadCount = 20; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean isRunning() { + return running; + } + + public void setRunning(boolean running) { + this.running = running; + } + + public PushConsumer getPushConsumer() { + return pushConsumer; + } + + public void setPushConsumer(PushConsumer pushConsumer) { + this.pushConsumer = pushConsumer; + } + + public PushConsumerBuilder getPushConsumerBuilder() { + return pushConsumerBuilder; + } + + public void setPushConsumerBuilder(PushConsumerBuilder pushConsumerBuilder) { + this.pushConsumerBuilder = pushConsumerBuilder; + } + + public String getAccessKey() { + return accessKey; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public String getEndpoints() { + return endpoints; + } + + public void setEndpoints(String endpoints) { + this.endpoints = endpoints; + } + + public String getConsumerGroup() { + return consumerGroup; + } + + public void setConsumerGroup(String consumerGroup) { + this.consumerGroup = consumerGroup; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public Duration getRequestTimeout() { + return requestTimeout; + } + + public void setRequestTimeout(Duration requestTimeout) { + this.requestTimeout = requestTimeout; + } + + public FilterExpressionType getFilterExpressionType() { + return filterExpressionType; + } + + public void setFilterExpressionType(FilterExpressionType filterExpressionType) { + this.filterExpressionType = filterExpressionType; + } + + public int getMaxCachedMessageCount() { + return maxCachedMessageCount; + } + + public void setMaxCachedMessageCount(int maxCachedMessageCount) { + this.maxCachedMessageCount = maxCachedMessageCount; + } + + public int getMaxCacheMessageSizeInBytes() { + return maxCacheMessageSizeInBytes; + } + + public void setMaxCacheMessageSizeInBytes(int maxCacheMessageSizeInBytes) { + this.maxCacheMessageSizeInBytes = maxCacheMessageSizeInBytes; + } + + public int getConsumptionThreadCount() { + return consumptionThreadCount; + } + + public void setConsumptionThreadCount(int consumptionThreadCount) { + this.consumptionThreadCount = consumptionThreadCount; + } + + public RocketMQListener getMessageListener() { + return rocketMQListener; + } + + public void setMessageListener(RocketMQListener rocketMQListener) { + this.rocketMQListener = rocketMQListener; + } + + public RocketMQMessageListener getRocketMQMessageListener() { + return rocketMQMessageListener; + } + + public void setRocketMQMessageListener(RocketMQMessageListener rocketMQMessageListener) { + this.rocketMQMessageListener = rocketMQMessageListener; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + private void initRocketMQPushConsumer() { + if (rocketMQMessageListener == null) { + throw new IllegalArgumentException("Property 'rocketMQMessageListener' is required"); + } + Assert.notNull(consumerGroup, "Property 'consumerGroup' is required"); + Assert.notNull(topic, "Property 'topic' is required"); + Assert.notNull(tag, "Property 'tag' is required"); + FilterExpression filterExpression = null; + final ClientServiceProvider provider = ClientServiceProvider.loadService(); + if (StringUtils.hasLength(this.getTag())) { + filterExpression = RocketMQUtil.createFilterExpression(this.getTag(),this.getType()); + } + ClientConfiguration clientConfiguration = RocketMQUtil.createClientConfiguration(this.getAccessKey(), this.getSecretKey(), this.getEndpoints(), this.getRequestTimeout()); + + PushConsumerBuilder pushConsumerBuilder = provider.newPushConsumerBuilder() + .setClientConfiguration(clientConfiguration); + // Set the consumer group name. + if (StringUtils.hasLength(this.getConsumerGroup())) { + pushConsumerBuilder.setConsumerGroup(this.getConsumerGroup()); + } + // Set the subscription for the consumer. + if (StringUtils.hasLength(this.getTopic()) && Objects.nonNull(filterExpression)) { + pushConsumerBuilder.setSubscriptionExpressions(Collections.singletonMap(this.getTopic(), filterExpression)); + } + pushConsumerBuilder + .setConsumptionThreadCount(this.getConsumptionThreadCount()) + .setMaxCacheMessageSizeInBytes(this.getMaxCacheMessageSizeInBytes()) + .setMaxCacheMessageCount(this.getMaxCachedMessageCount()) + .setMessageListener(rocketMQListener); + this.setPushConsumerBuilder(pushConsumerBuilder); + } + + + @Override + public boolean isAutoStartup() { + return true; + } + + @Override + public void stop(Runnable callback) { + stop(); + callback.run(); + } + + @Override + public void destroy() throws Exception { + this.setRunning(false); + if (Objects.nonNull(pushConsumer)) { + pushConsumer.close(); + } + log.info("container destroyed, {}", this.toString()); + } + + @Override + public void stop() { + if (this.isRunning()) { + if (Objects.nonNull(pushConsumer)) { + try { + pushConsumer.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + setRunning(false); + } + } + + @Override + public void start() { + if (this.isRunning()) { + throw new IllegalStateException("container already running. " + name); + } + if (Objects.nonNull(pushConsumer)) { + throw new IllegalStateException("consumer has been build. " + name); + } + try { + this.pushConsumer = pushConsumerBuilder.build(); + } catch (Exception e) { + throw new IllegalStateException("Failed to start RocketMQ push consumer", e); + } + this.setRunning(true); + + log.info("running container: {}", this.toString()); + } + + + @Override + public void afterPropertiesSet() throws Exception { + initRocketMQPushConsumer(); + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + @Override + public String toString() { + return "DefaultListenerContainer{" + + "name='" + name + '\'' + + ", running=" + running + + ", accessKey='" + accessKey + '\'' + + ", secretKey='" + secretKey + '\'' + + ", endpoints='" + endpoints + '\'' + + ", consumerGroup='" + consumerGroup + '\'' + + ", tag='" + tag + '\'' + + ", topic='" + topic + '\'' + + ", type='" + type + '\'' + + ", filterExpressionType=" + filterExpressionType + + ", requestTimeout=" + requestTimeout + + ", maxCachedMessageCount=" + maxCachedMessageCount + + ", maxCacheMessageSizeInBytes=" + maxCacheMessageSizeInBytes + + ", consumptionThreadCount=" + consumptionThreadCount + + '}'; + } +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQHeaders.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQHeaders.java new file mode 100644 index 00000000..9d0145ca --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQHeaders.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.support; + +public class RocketMQHeaders { + public static final String PREFIX = "rocketmq_"; + public static final String KEYS = "KEYS"; + public static final String TAGS = "TAGS"; + public static final String TOPIC = "TOPIC"; + public static final String MESSAGE_ID = "MESSAGE_ID"; + public static final String BORN_TIMESTAMP = "BORN_TIMESTAMP"; + public static final String BORN_HOST = "BORN_HOST"; + public static final String FLAG = "FLAG"; + public static final String QUEUE_ID = "QUEUE_ID"; + public static final String SYS_FLAG = "SYS_FLAG"; + public static final String TRANSACTION_ID = "TRANSACTION_ID"; + public static final String DELAY = "DELAY"; + public static final String WAIT = "WAIT"; +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQListenerContainer.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQListenerContainer.java new file mode 100644 index 00000000..9473761f --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQListenerContainer.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.support; + +import org.springframework.beans.factory.DisposableBean; + +public interface RocketMQListenerContainer extends DisposableBean { +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQMessageConverter.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQMessageConverter.java new file mode 100644 index 00000000..5ede82d2 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQMessageConverter.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.support; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.messaging.converter.CompositeMessageConverter; +import org.springframework.messaging.converter.ByteArrayMessageConverter; +import org.springframework.messaging.converter.MessageConverter; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.converter.StringMessageConverter; +import org.springframework.util.ClassUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * @see MessageConverter + * @see CompositeMessageConverter + */ +public class RocketMQMessageConverter { + private static final boolean JACKSON_PRESENT; + private static final boolean FASTJSON_PRESENT; + + static { + ClassLoader classLoader = RocketMQMessageConverter.class.getClassLoader(); + JACKSON_PRESENT = + ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) && + ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader); + FASTJSON_PRESENT = ClassUtils.isPresent("com.alibaba.fastjson.JSON", classLoader) && + ClassUtils.isPresent("com.alibaba.fastjson.support.config.FastJsonConfig", classLoader); + } + + private final CompositeMessageConverter messageConverter; + + public RocketMQMessageConverter() { + List messageConverters = new ArrayList<>(); + ByteArrayMessageConverter byteArrayMessageConverter = new ByteArrayMessageConverter(); + byteArrayMessageConverter.setContentTypeResolver(null); + messageConverters.add(byteArrayMessageConverter); + messageConverters.add(new StringMessageConverter()); + if (JACKSON_PRESENT) { + MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); + ObjectMapper mapper = converter.getObjectMapper(); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.registerModule(new JavaTimeModule()); + converter.setObjectMapper(mapper); + messageConverters.add(converter); + } + if (FASTJSON_PRESENT) { + try { + messageConverters.add( + (org.springframework.messaging.converter.MessageConverter) ClassUtils.forName( + "com.alibaba.fastjson.support.spring.messaging.MappingFastJsonMessageConverter", + ClassUtils.getDefaultClassLoader()).newInstance()); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException ignored) { + //ignore this exception + } + } + messageConverter = new CompositeMessageConverter(messageConverters); + } + + public org.springframework.messaging.converter.MessageConverter getMessageConverter() { + return messageConverter; + } + + +} diff --git a/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQUtil.java b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQUtil.java new file mode 100644 index 00000000..10d977aa --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/java/org/apache/rocketmq/client/support/RocketMQUtil.java @@ -0,0 +1,169 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.rocketmq.client.support; + +import org.apache.rocketmq.client.apis.ClientConfiguration; +import org.apache.rocketmq.client.apis.ClientServiceProvider; +import org.apache.rocketmq.client.apis.SessionCredentialsProvider; +import org.apache.rocketmq.client.apis.StaticSessionCredentialsProvider; +import org.apache.rocketmq.client.apis.ClientConfigurationBuilder; +import org.apache.rocketmq.client.autoconfigure.RocketMQProperties; + +import org.apache.rocketmq.client.apis.consumer.FilterExpression; +import org.apache.rocketmq.client.apis.consumer.FilterExpressionType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.converter.MessageConverter; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; + +import java.nio.charset.Charset; +import java.time.Duration; +import java.util.Objects; + +public class RocketMQUtil { + + private static final Logger log = LoggerFactory.getLogger(RocketMQUtil.class); + + public static org.apache.rocketmq.client.apis.message.Message convertToClientMessage( + MessageConverter messageConverter, String charset, + String destination, org.springframework.messaging.Message message, Duration messageDelayTime, String messageGroup) { + Object payloadObject = message.getPayload(); + byte[] payloads; + try { + payloads = getPayloadBytes(payloadObject, messageConverter, charset, message); + } catch (Exception e) { + throw new RuntimeException("convert to gRPC message failed.", e); + } + return getAndWrapMessage(destination, message.getHeaders(), payloads, messageDelayTime, messageGroup); + } + + public static org.apache.rocketmq.client.apis.message.Message getAndWrapMessage( + String destination, MessageHeaders headers, byte[] payloads, Duration messageDelayTime, String messageGroup) { + if (payloads == null || payloads.length < 1) { + return null; + } + if (destination == null || destination.length() < 1) { + return null; + } + String[] tempArr = destination.split(":", 2); + final ClientServiceProvider provider = ClientServiceProvider.loadService(); + org.apache.rocketmq.client.apis.message.MessageBuilder messageBuilder = null; + // resolve header + if (Objects.nonNull(headers) && !headers.isEmpty()) { + Object keys = headers.get(RocketMQHeaders.KEYS); + if (ObjectUtils.isEmpty(keys)) { + keys = headers.get(toRocketHeaderKey(RocketMQHeaders.KEYS)); + } + messageBuilder = provider.newMessageBuilder() + .setTopic(tempArr[0]); + if (tempArr.length > 1) { + messageBuilder.setTag(tempArr[1]); + } + if (StringUtils.hasLength(messageGroup)) { + messageBuilder.setMessageGroup(messageGroup); + } + if (!ObjectUtils.isEmpty(keys)) { + messageBuilder.setKeys(keys.toString()); + } + if (Objects.nonNull(messageDelayTime)) { + messageBuilder.setDeliveryTimestamp(System.currentTimeMillis() + messageDelayTime.toMillis()); + } + messageBuilder.setBody(payloads); + org.apache.rocketmq.client.apis.message.MessageBuilder builder = messageBuilder; + headers.forEach((key, value) -> builder.addProperty(key, String.valueOf(value))); + } + return messageBuilder.build(); + } + + public static byte[] getPayloadBytes(Object payloadObj, MessageConverter messageConverter, String charset, org.springframework.messaging.Message message) { + byte[] payloads; + if (null == payloadObj) { + throw new RuntimeException("the message cannot be empty"); + } + if (payloadObj instanceof String) { + payloads = ((String) payloadObj).getBytes(Charset.forName(charset)); + } else if (payloadObj instanceof byte[]) { + payloads = (byte[]) message.getPayload(); + } else { + String jsonObj = (String) messageConverter.fromMessage(message, payloadObj.getClass()); + if (null == jsonObj) { + throw new RuntimeException(String.format( + "empty after conversion [messageConverter:%s,payloadClass:%s,payloadObj:%s]", + messageConverter.getClass(), payloadObj.getClass(), payloadObj)); + } + payloads = jsonObj.getBytes(Charset.forName(charset)); + } + return payloads; + } + + public static String toRocketHeaderKey(String rawKey) { + return RocketMQHeaders.PREFIX + rawKey; + } + + public static ClientConfiguration createProducerClientConfiguration(RocketMQProperties.Producer rocketMQProducer) { + String accessKey = rocketMQProducer.getAccessKey(); + String secretKey = rocketMQProducer.getSecretKey(); + String endPoints = rocketMQProducer.getEndpoints(); + Duration requestTimeout = Duration.ofDays(rocketMQProducer.getRequestTimeout()); + // boolean sslEnabled = rocketMQProducer.isSslEnabled(); + return createClientConfiguration(accessKey, secretKey, endPoints, requestTimeout); + } + + public static ClientConfiguration createConsumerClientConfiguration(RocketMQProperties.SimpleConsumer simpleConsumer) { + String accessKey = simpleConsumer.getAccessKey(); + String secretKey = simpleConsumer.getSecretKey(); + String endPoints = simpleConsumer.getEndpoints(); + Duration requestTimeout = Duration.ofDays(simpleConsumer.getRequestTimeout()); + // boolean sslEnabled = rocketMQProducer.isSslEnabled(); + return createClientConfiguration(accessKey, secretKey, endPoints, requestTimeout); + + } + + public static ClientConfiguration createClientConfiguration(String accessKey, String secretKey, String endPoints, Duration requestTimeout) { + + SessionCredentialsProvider sessionCredentialsProvider = null; + if (StringUtils.hasLength(accessKey) && StringUtils.hasLength(secretKey)) { + sessionCredentialsProvider = + new StaticSessionCredentialsProvider(accessKey, secretKey); + } + ClientConfigurationBuilder clientConfigurationBuilder = ClientConfiguration.newBuilder() + .setEndpoints(endPoints); + if (sessionCredentialsProvider != null) { + clientConfigurationBuilder.setCredentialProvider(sessionCredentialsProvider); + } + if (Objects.nonNull(requestTimeout)) { + clientConfigurationBuilder.setRequestTimeout(requestTimeout); + } + return clientConfigurationBuilder.build(); + } + + + public static FilterExpression createFilterExpression(String tag, String type) { + if (!StringUtils.hasLength(tag) && !StringUtils.hasLength(type)) { + log.info("no filterExpression generate"); + return null; + } + if (!"tag".equalsIgnoreCase(type) && !"sql92".equalsIgnoreCase(type)) { + log.info("do not support your filterExpressionType {}", type); + } + FilterExpressionType filterExpressionType = "tag".equalsIgnoreCase(type) ? FilterExpressionType.TAG : FilterExpressionType.SQL92; + FilterExpression filterExpression = new FilterExpression(tag, filterExpressionType); + return filterExpression; + } +} diff --git a/rocketmq-v5-client-spring-boot/src/main/resources/META-INF/spring.factories b/rocketmq-v5-client-spring-boot/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..872f2288 --- /dev/null +++ b/rocketmq-v5-client-spring-boot/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.apache.rocketmq.client.autoconfigure.RocketMQAutoConfiguration