diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5799172d..870aadbb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -12,9 +12,9 @@ XXXX Follow this checklist to help us incorporate your contribution quickly and easily. Notice, `it would be helpful if you could finish the following 5 checklist(the last one is not necessary)before request the community to review your PR`. -- [x] Make sure there is a [Github issue](https://github.com/apache/rocketmq/issues) filed for the change (usually before you start working on it). Trivial changes like typos do not require a Github issue. Your pull request should address just this issue, without pulling in other changes - one PR resolves one issue. -- [x] Format the pull request title like `[ISSUE #123] Fix UnknownException when host config not exist`. Each commit in the pull request should have a meaningful subject line and body. -- [x] Write a pull request description that is detailed enough to understand what the pull request does, how, and why. -- [x] Write necessary unit-test(over 80% coverage) to verify your logic correction, more mock a little better when cross module dependency exist. -- [x] Run `mvn -B clean apache-rat:check findbugs:findbugs checkstyle:checkstyle` to make sure basic checks pass. Run `mvn clean install -DskipITs` to make sure unit-test pass. Run `mvn clean test-compile failsafe:integration-test` to make sure integration-test pass. -- [ ] If this contribution is large, please file an [Apache Individual Contributor License Agreement](http://www.apache.org/licenses/#clas). +- [] Make sure there is a [Github issue](https://github.com/apache/rocketmq/issues) filed for the change (usually before you start working on it). Trivial changes like typos do not require a Github issue. Your pull request should address just this issue, without pulling in other changes - one PR resolves one issue. +- [] Format the pull request title like `[ISSUE #123] Fix UnknownException when host config not exist`. Each commit in the pull request should have a meaningful subject line and body. +- [] Write a pull request description that is detailed enough to understand what the pull request does, how, and why. +- [] Write necessary unit-test(over 80% coverage) to verify your logic correction, more mock a little better when cross module dependency exist. +- [] Run `mvn -B clean apache-rat:check findbugs:findbugs checkstyle:checkstyle` to make sure basic checks pass. Run `mvn clean install -DskipITs` to make sure unit-test pass. Run `mvn clean test-compile failsafe:integration-test` to make sure integration-test pass. +- [] If this contribution is large, please file an [Apache Individual Contributor License Agreement](http://www.apache.org/licenses/#clas). diff --git a/.travis.yml b/.travis.yml index 24e6a65a..49be497f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +dist: trusty + notifications: email: recipients: @@ -7,6 +9,9 @@ notifications: language: java +jdk: + - oraclejdk8 + matrix: include: # On OSX, run with default JDK only. diff --git a/NOTICE b/NOTICE index f07078d7..086ee9fa 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Apache RocketMQ -Copyright 2016-2018 The Apache Software Foundation +Copyright 2016-2021 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff --git a/README.md b/README.md index f8edadce..5e2594e0 100644 --- a/README.md +++ b/README.md @@ -3,48 +3,36 @@ [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.rocketmq/rocketmq-spring-all/badge.svg)](https://search.maven.org/search?q=g:org.apache.rocketmq%20AND%20a:rocketmq-spring-all) [![GitHub release](https://img.shields.io/badge/release-download-orange.svg)](https://github.com/apache/rocketmq-spring/releases) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) - -[中文](./README_zh_CN.md) - -## Introduction +[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/apache/rocketmq-spring.svg)](http://isitmaintained.com/project/apache/rocketmq-spring "Average time to resolve an issue") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/apache/rocketmq-spring.svg)](http://isitmaintained.com/project/apache/rocketmq-spring "Percentage of issues still open") This project aims to help developers quickly integrate [RocketMQ](http://rocketmq.apache.org/) with [Spring Boot](http://projects.spring.io/spring-boot/). -## How To Contribute - -We are always very happy to have contributions, whether for trivial cleanups or big new features. Please see the RocketMQ main website to read [details](http://rocketmq.apache.org/docs/how-to-contribute/) - +## Features + +- [x] Send messages synchronously +- [x] Send messages asynchronously +- [x] Send messages in one-way mode +- [x] Send ordered messages +- [x] Send batched messages +- [x] Send transactional messages +- [x] Send scheduled messages with delay level +- [x] Consume messages with concurrently mode (broadcasting/clustering) +- [x] Consume ordered messages +- [x] Filter messages using the tag or sql92 expression +- [x] Support message tracing +- [x] Support authentication and authorization +- [x] Support request-reply message exchange pattern +- [x] Consume messages with push/pull mode ## Prerequisites - JDK 1.8 and above - [Maven](http://maven.apache.org/) 3.0 and above +- Spring Boot 2.0 and above -## Build and Install with local maven repository - -``` - mvn clean install -``` - -## Features: - -- [x] synchronous transmission -- [x] synchronous ordered transmission -- [x] synchronous batch transmission -- [x] asynchronous transmission -- [x] asynchronous ordered transmission -- [x] orderly consume -- [x] concurrently consume(broadcasting/clustering) -- [x] one-way transmission -- [x] transaction transmission -- [x] message trace -- [x] ACL -- [ ] pull consume +## Usage -## Quick Start - -Please see the complete sample [rocketmq-spring-boot-samples](rocketmq-spring-boot-samples) - -Note: Current RELEASE.VERSION=2.0.3 +Add a dependency using maven: ```xml @@ -53,354 +41,20 @@ Note: Current RELEASE.VERSION=2.0.3 rocketmq-spring-boot-starter ${RELEASE.VERSION} -``` - -### Produce Message - -```properties -## application.properties -rocketmq.name-server=127.0.0.1:9876 -rocketmq.producer.group=my-group -``` - -> Note: -> -> Maybe you need change `127.0.0.1:9876` with your real NameServer address for RocketMQ - -```java -@SpringBootApplication -public class ProducerApplication implements CommandLineRunner{ - @Resource - private RocketMQTemplate rocketMQTemplate; - - public static void main(String[] args){ - SpringApplication.run(ProducerApplication.class, args); - } - - public void run(String... args) throws Exception { - rocketMQTemplate.convertAndSend("test-topic-1", "Hello, World!"); - rocketMQTemplate.send("test-topic-1", MessageBuilder.withPayload("Hello, World! I'm from spring message").build()); - rocketMQTemplate.convertAndSend("test-topic-2", new OrderPaidEvent("T_001", new BigDecimal("88.00"))); - -// rocketMQTemplate.destroy(); // notes: once rocketMQTemplate be destroyed, you can not send any message again with this rocketMQTemplate - } - - @Data - @AllArgsConstructor - public class OrderPaidEvent implements Serializable{ - private String orderId; - - private BigDecimal paidMoney; - } -} -``` - -> More relevant configurations for producing: -> -> ```properties -> rocketmq.producer.send-message-timeout=300000 -> rocketmq.producer.compress-message-body-threshold=4096 -> rocketmq.producer.max-message-size=4194304 -> rocketmq.producer.retry-times-when-send-async-failed=0 -> rocketmq.producer.retry-next-server=true -> rocketmq.producer.retry-times-when-send-failed=2 -> ``` - - -### Send message in transaction and implement local check Listener -```java -@SpringBootApplication -public class ProducerApplication implements CommandLineRunner{ - @Resource - private RocketMQTemplate rocketMQTemplate; - - public static void main(String[] args){ - SpringApplication.run(ProducerApplication.class, args); - } - - public void run(String... args) throws Exception { - try { - // Build a SpringMessage for sending in transaction - Message msg = MessageBuilder.withPayload(..)...; - // In sendMessageInTransaction(), the first parameter transaction name ("test") - // must be same with the @RocketMQTransactionListener's member field 'transName' - rocketMQTemplate.sendMessageInTransaction("test", "test-topic", msg, null); - } catch (MQClientException e) { - e.printStackTrace(System.out); - } - } - - // Define transaction listener with the annotation @RocketMQTransactionListener - @RocketMQTransactionListener(transName="test") - class TransactionListenerImpl implements RocketMQLocalTransactionListener { - @Override - public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) { - // ... local transaction process, return bollback, commit or unknown - return RocketMQLocalTransactionState.UNKNOWN; - } - - @Override - public RocketMQLocalTransactionState checkLocalTransaction(Message msg) { - // ... check transaction status and return bollback, commit or unknown - return RocketMQLocalTransactionState.COMMIT; - } - } -} -``` - -### Consume Message - -```properties -## application.properties -rocketmq.name-server=127.0.0.1:9876 -``` - -> Note: -> -> Maybe you need change `127.0.0.1:9876` with your real NameServer address for RocketMQ - -```java -@SpringBootApplication -public class ConsumerApplication{ - - public static void main(String[] args){ - SpringApplication.run(ConsumerApplication.class, args); - } - - @Slf4j - @Service - @RocketMQMessageListener(topic = "test-topic-1", consumerGroup = "my-consumer_test-topic-1") - public class MyConsumer1 implements RocketMQListener{ - public void onMessage(String message) { - log.info("received message: {}", message); - } - } - - @Slf4j - @Service - @RocketMQMessageListener(topic = "test-topic-2", consumerGroup = "my-consumer_test-topic-2") - public class MyConsumer2 implements RocketMQListener{ - public void onMessage(OrderPaidEvent orderPaidEvent) { - log.info("received orderPaidEvent: {}", orderPaidEvent); - } - } -} -``` - -> More relevant configurations for consuming: -> -> see: [RocketMQMessageListener](rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/RocketMQMessageListener.java) - -### Message Trace - -We need 2 more configurations for support message trace in producer. - -```properties -## application.properties -rocketmq.name-server=127.0.0.1:9876 -rocketmq.producer.group=my-group - -rocketmq.producer.enable-msg-trace=true -rocketmq.producer.customized-trace-topic=my-trace-topic -``` - -The message trace in consumer should configure in `@RocketMQMessageListener`. - -``` -@Service -@RocketMQMessageListener( - topic = "test-topic-1", - consumerGroup = "my-consumer_test-topic-1", - enableMsgTrace = true, - customizedTraceTopic = "my-trace-topic" -) -public class MyConsumer implements RocketMQListener { - ... -} -``` - - -> Note: -> -> Maybe you need change `127.0.0.1:9876` with your real NameServer address for RocketMQ - -> By default, the message track feature of Producer and Consumer is turned on and the trace-topic is RMQ_SYS_TRACE_TOPIC -> The topic of message trace can be configured with `rocketmq.consumer.customized-trace-topic` configuration item, not required to be configured in each `@RocketMQTransactionListener` - - -### ACL - -We need 2 more configurations for support ACL in producer. - -```properties -## application.properties -rocketmq.name-server=127.0.0.1:9876 -rocketmq.producer.group=my-group - -rocketmq.producer.access-key=AK -rocketmq.producer.secret-key=SK -``` -Transaction Message should configure AK/SK in `@RocketMQTransactionListener`. - -``` -@RocketMQTransactionListener( - txProducerGroup = "test, - accessKey = "AK", - secretKey = "SK" -) -class TransactionListenerImpl implements RocketMQLocalTransactionListener { - ... -} -``` - -> Note: -> -> You do not need to configure AK/SK for each `@RocketMQTransactionListener`, you could configure `rocketmq.producer.access-key` and `rocketmq.producer.secret-key` as default value - -The ACL feature in consumer should configure AK/SK in `@RocketMQMessageListener`. - -``` -@Service -@RocketMQMessageListener( - topic = "test-topic-1", - consumerGroup = "my-consumer_test-topic-1", - accessKey = "AK", - secretKey = "SK" -) -public class MyConsumer implements RocketMQListener { - ... -} -``` - -> Note: -> -> You do not need to configure AK/SK for each `@RocketMQMessageListener`, you could configure `rocketmq.consumer.access-key` and `rocketmq.consumer.secret-key` as default value - -## FAQ - -1. How to connected many `nameserver` on production environment? - - `rocketmq.name-server` support the configuration of multiple `nameserver`, separated by `;`. For example: `172.19.0.1: 9876; 172.19.0.2: 9876` - -1. When was `rocketMQTemplate` destroyed? - - Developers do not need to manually execute the `rocketMQTemplate.destroy ()` method when using `rocketMQTemplate` to send a message in the project, and` rocketMQTemplate` will be destroyed automatically when the spring container is destroyed. - -1. start exception:`Caused by: org.apache.rocketmq.client.exception.MQClientException: The consumer group[xxx] has been created before, specify another name please` - - RocketMQ in the design do not want a consumer to deal with multiple types of messages at the same time, so the same `consumerGroup` consumer responsibility should be the same, do not do different things (that is, consumption of multiple topics). Suggested `consumerGroup` and` topic` one correspondence. - -1. How is the message content body being serialized and deserialized? - - RocketMQ's message body is stored as `byte []`. When the business system message content body if it is `java.lang.String` type, unified in accordance with` utf-8` code into `byte []`; If the business system message content is not `java.lang.String` Type, then use [jackson-databind](https://github.com/FasterXML/jackson-databind) serialized into the `JSON` format string, and then unified in accordance with` utf-8` code into `byte [] `. - -1. How do I specify the `tags` for topic? +``` - RocketMQ best practice recommended: an application as much as possible with one Topic, the message sub-type with `tags` to identify,` tags` can be set by the application free. - - When you use `rocketMQTemplate` to send a message, set the destination of the message by setting the` destination` parameter of the send method. The `destination` format is `topicName:tagName`, `:` Precedes the name of the topic, followed by the `tags` name. - - > Note: - > - > `tags` looks a complex, but when sending a message , the destination can only specify one topic under a `tag`, can not specify multiple. - -1. How do I set the message's `key` when sending a message? +## Samples - You can send a message by overloading method like `xxxSend(String destination, Message msg, ...)`, setting `headers` of `msg`. for example: - - ```java - Message message = MessageBuilder.withPayload(payload).setHeader(MessageConst.PROPERTY_KEYS, msgId).build(); - rocketMQTemplate.send("topic-test", message); - ``` +Please see the [rocketmq-spring-boot-samples](rocketmq-spring-boot-samples). - Similarly, you can also set the message `FLAG`,` WAIT_STORE_MSG_OK` and some other user-defined other header information according to the above method. - - > Note: - > - > In the case of converting Spring's Message to RocketMQ's Message, to prevent the `header` information from conflicting with RocketMQ's system properties, the prefix `USERS_` was added in front of all `header` names. So if you want to get a custom message header when consuming, please pass through the key at the beginning of `USERS_` in the header. - -1. When consume message, in addition to get the message `payload`, but also want to get RocketMQ message of other system attributes, how to do? +## User Guide - Consumers in the realization of `RocketMQListener` interface, only need to be generic for the` MessageExt` can, so in the `onMessage` method will receive RocketMQ native 'MessageExt` message. - - ```java - @Slf4j - @Service - @RocketMQMessageListener(topic = "test-topic-1", consumerGroup = "my-consumer_test-topic-1") - public class MyConsumer2 implements RocketMQListener{ - public void onMessage(MessageExt messageExt) { - log.info("received messageExt: {}", messageExt); - } - } - ``` - -1. How do I specify where consumers start consuming messages? +Please see the [wiki](https://github.com/apache/rocketmq-spring/wiki) page. - The default consume offset please refer: [RocketMQ FAQ](http://rocketmq.apache.org/docs/faq/). - To customize the consumer's starting location, simply add a `RocketMQPushConsumerLifecycleListener` interface implementation to the consumer class. Examples are as follows: - - ```java - @Slf4j - @Service - @RocketMQMessageListener(topic = "test-topic-1", consumerGroup = "my-consumer_test-topic-1") - public class MyConsumer1 implements RocketMQListener, RocketMQPushConsumerLifecycleListener { - @Override - public void onMessage(String message) { - log.info("received message: {}", message); - } - - @Override - public void prepareStart(final DefaultMQPushConsumer consumer) { - // set consumer consume message from now - consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP); - consumer.setConsumeTimestamp(UtilAll.timeMillisToHumanString3(System.currentTimeMillis())); - } - } - ``` - - Similarly, any other configuration on `DefaultMQPushConsumer` can be done in the same way as above. +## Contributing -1. How do I send transactional messages? - It needs two steps on client side: - - a) Define a class which is annotated with @RocketMQTransactionListener and implements RocketMQLocalTransactionListener interface, in which, the executeLocalTransaction() and checkLocalTransaction() methods are implemented; - - b) Invoke the sendMessageInTransaction() method with the RocketMQTemplate API. Note: The first parameter of this method is correlated with the txProducerGroup attribute of @RocketMQTransactionListener. It can be null if using the default transaction producer group. +We are always very happy to have contributions, whether for trivial cleanups or big new features. Please see the RocketMQ main website to read [details](http://rocketmq.apache.org/docs/how-to-contribute/). -1. How do I create more than one RocketMQTemplate with a different name-server or other specific properties? - ```java - // Step1. Define an extra RocketMQTemplate with required properties, note, the 'nameServer' property must be different from the value of global - // Spring configuration 'rocketmq.name-server', other properties are optionally defined, they will use the global configuration - // definition by default. - - // The RocketMQTemplate's Spring Bean name is 'extRocketMQTemplate', same with the simplified class name (Initials lowercase) - @ExtRocketMQTemplateConfiguration(nameServer="127.0.0.1:9876" - , ... // override other specific properties if needed - ) - public class ExtRocketMQTemplate extends RocketMQTemplate { - // keep the body empty - } - - - // Step2. Use the extra RocketMQTemplate. e.g. - @Resource(name = "extRocketMQTemplate") // Must define the name to qualify to extra-defined RocketMQTemplate bean. - private RocketMQTemplate extRocketMQTemplate; - // you can use the template as normal. - - ``` - -1. How do I create a consumer Listener with different name-server other than the global Spring configuration 'rocketmq.name-server' ? - ```java - @Service - @RocketMQMessageListener( - nameServer = "NEW-NAMESERVER-LIST", // define new nameServer list - topic = "test-topic-1", - consumerGroup = "my-consumer_test-topic-1", - enableMsgTrace = true, - customizedTraceTopic = "my-trace-topic" - ) - public class MyNameServerConsumer implements RocketMQListener { - ... - } - ``` \ No newline at end of file +## License +[Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) Copyright (C) Apache Software Foundation diff --git a/README_zh_CN.md b/README_zh_CN.md deleted file mode 100644 index fe26aa8b..00000000 --- a/README_zh_CN.md +++ /dev/null @@ -1,391 +0,0 @@ -# RocketMQ-Spring [![Build Status](https://travis-ci.org/apache/rocketmq-spring.svg?branch=master)](https://travis-ci.org/apache/rocketmq-spring) - -[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.rocketmq/rocketmq-spring-all/badge.svg)](https://search.maven.org/search?q=g:org.apache.rocketmq%20AND%20a:rocketmq-spring-all) -[![GitHub release](https://img.shields.io/badge/release-download-orange.svg)](https://github.com/apache/rocketmq-spring/releases) -[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) - -[English](./README.md) - -帮助开发者在[Spring Boot](http://projects.spring.io/spring-boot/)中快速集成[RocketMQ](http://rocketmq.apache.org/)。支持Spring Message规范,方便开发者从其它MQ快速切换到RocketMQ。 - -## 如何贡献和帮助社区 - -我们永远欢迎开发者的帮助来使这个项目更加完善,无论是小的文档还是大的功能新特性,请参考RocketMQ的主站了解[细节](http://rocketmq.apache.org/docs/how-to-contribute/) - -## 前提条件 -- JDK 1.8 and above -- [Maven](http://maven.apache.org/) 3.0 and above - -功能特性: - -- [x] 同步发送 -- [x] 同步顺序发送 -- [x] 同步批量发送 -- [x] 异步发送 -- [x] 异步顺序发送 -- [x] 顺序消费 -- [x] 并发消费(广播/集群) -- [x] one-way方式发送 -- [x] 事务方式发送 -- [x] 消息轨迹 -- [x] ACL -- [ ] pull消费 - -## Quick Start - -下面列出来了一些关键点,完整的示例请参考: [rocketmq-spring-boot-samples](rocketmq-spring-boot-samples) - -注意:当前的RELEASE.VERSION=2.0.3 - -```xml - - - org.apache.rocketmq - rocketmq-spring-boot-starter - ${RELEASE.VERSION} - -``` - -### 发送消息 - -```properties -## application.properties -rocketmq.name-server=127.0.0.1:9876 -rocketmq.producer.group=my-group -``` - -> 注意: -> -> 请将上述示例配置中的`127.0.0.1:9876`替换成真实RocketMQ的NameServer地址与端口 - -```java -@SpringBootApplication -public class ProducerApplication implements CommandLineRunner{ - @Resource - private RocketMQTemplate rocketMQTemplate; - - public static void main(String[] args){ - SpringApplication.run(ProducerApplication.class, args); - } - - public void run(String... args) throws Exception { - rocketMQTemplate.convertAndSend("test-topic-1", "Hello, World!"); - rocketMQTemplate.send("test-topic-1", MessageBuilder.withPayload("Hello, World! I'm from spring message").build()); - rocketMQTemplate.convertAndSend("test-topic-2", new OrderPaidEvent("T_001", new BigDecimal("88.00"))); - -// rocketMQTemplate.destroy(); // notes: once rocketMQTemplate be destroyed, you can not send any message again with this rocketMQTemplate - } - - @Data - @AllArgsConstructor - public class OrderPaidEvent implements Serializable{ - private String orderId; - - private BigDecimal paidMoney; - } -} -``` - -### 在发送客户端发送事务性消息并且实现回查Listener -```java -@SpringBootApplication -public class ProducerApplication implements CommandLineRunner{ - @Resource - private RocketMQTemplate rocketMQTemplate; - - public static void main(String[] args){ - SpringApplication.run(ProducerApplication.class, args); - } - - public void run(String... args) throws Exception { - try { - // Build a SpringMessage for sending in transaction - Message msg = MessageBuilder.withPayload(..)... - // In sendMessageInTransaction(), the first parameter transaction name ("test") - // must be same with the @RocketMQTransactionListener's member field 'transName' - rocketMQTemplate.sendMessageInTransaction("test", "test-topic" msg, null); - } catch (MQClientException e) { - e.printStackTrace(System.out); - } - } - - // Define transaction listener with the annotation @RocketMQTransactionListener - @RocketMQTransactionListener(transName="test") - class TransactionListenerImpl implements RocketMQLocalTransactionListener { - @Override - public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) { - // ... local transaction process, return bollback, commit or unknown - return RocketMQLocalTransactionState.UNKNOWN; - } - - @Override - public RocketMQLocalTransactionState checkLocalTransaction(Message msg) { - // ... check transaction status and return bollback, commit or unknown - return RocketMQLocalTransactionState.COMMIT; - } - } -} -``` - -> 更多发送相关配置 -> -> ```properties -> rocketmq.producer.send-message-timeout=300000 -> rocketmq.producer.compress-message-body-threshold=4096 -> rocketmq.producer.max-message-size=4194304 -> rocketmq.producer.retry-times-when-send-async-failed=0 -> rocketmq.producer.retry-next-server=true -> rocketmq.producer.retry-times-when-send-failed=2 -> ``` - - -### 接收消息 - -```properties -## application.properties -rocketmq.name-server=127.0.0.1:9876 -``` - -> 注意: -> -> 请将上述示例配置中的`127.0.0.1:9876`替换成真实RocketMQ的NameServer地址与端口 - -```java -@SpringBootApplication -public class ConsumerApplication{ - - public static void main(String[] args){ - SpringApplication.run(ConsumerApplication.class, args); - } - - @Slf4j - @Service - @RocketMQMessageListener(topic = "test-topic-1", consumerGroup = "my-consumer_test-topic-1") - public class MyConsumer1 implements RocketMQListener{ - public void onMessage(String message) { - log.info("received message: {}", message); - } - } - - @Slf4j - @Service - @RocketMQMessageListener(topic = "test-topic-2", consumerGroup = "my-consumer_test-topic-2") - public class MyConsumer2 implements RocketMQListener{ - public void onMessage(OrderPaidEvent orderPaidEvent) { - log.info("received orderPaidEvent: {}", orderPaidEvent); - } - } -} -``` - - -> 更多消费相关配置 -> -> see: [RocketMQMessageListener](rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/RocketMQMessageListener.java) - -### 消息轨迹 - -Producer 端要想使用消息轨迹,需要多配置两个配置项: - -```properties -## application.properties -rocketmq.name-server=127.0.0.1:9876 -rocketmq.producer.group=my-group - -rocketmq.producer.enable-msg-trace=true -rocketmq.producer.customized-trace-topic=my-trace-topic -``` - -Consumer 端消息轨迹的功能需要在 `@RocketMQMessageListener` 中进行配置对应的属性: - -``` -@Service -@RocketMQMessageListener( - topic = "test-topic-1", - consumerGroup = "my-consumer_test-topic-1", - enableMsgTrace = true, - customizedTraceTopic = "my-trace-topic" -) -public class MyConsumer implements RocketMQListener { - ... -} -``` - -> 注意: -> -> 默认情况下 Producer 和 Consumer 的消息轨迹功能是开启的且 trace-topic 为 RMQ_SYS_TRACE_TOPIC -> Consumer 端的消息轨迹 trace-topic 可以在配置文件中配置 `rocketmq.consumer.customized-trace-topic` 配置项,不需要为在每个 `@RocketMQTransactionListener` 配置 - - -### ACL - -Producer 端要想使用 ACL 功能,需要多配置两个配置项: - -```properties -## application.properties -rocketmq.name-server=127.0.0.1:9876 -rocketmq.producer.group=my-group - -rocketmq.producer.access-key=AK -rocketmq.producer.secret-key=SK -``` - -事务消息的发送需要在 `@RocketMQTransactionListener` 注解里配置上 AK/SK: - -``` -@RocketMQTransactionListener( - txProducerGroup = "test, - accessKey = "AK", - secretKey = "SK" -) -class TransactionListenerImpl implements RocketMQLocalTransactionListener { - ... -} -``` - -> 注意: -> -> 可以不用为每个 `@RocketMQTransactionListener` 注解配置 AK/SK,在配置文件中配置 `rocketmq.producer.access-key` 和 `rocketmq.producer.secret-key` 配置项,这两个配置项的值就是默认值 - -Consumer 端 ACL 功能需要在 `@RocketMQMessageListener` 中进行配置 - -``` -@Service -@RocketMQMessageListener( - topic = "test-topic-1", - consumerGroup = "my-consumer_test-topic-1", - accessKey = "AK", - secretKey = "SK" -) -public class MyConsumer implements RocketMQListener { - ... -} -``` - -> 注意: -> -> 可以不用为每个 `@RocketMQMessageListener` 注解配置 AK/SK,在配置文件中配置 `rocketmq.consumer.access-key` 和 `rocketmq.consumer.secret-key` 配置项,这两个配置项的值就是默认值 - -## FAQ - -1. 生产环境有多个`nameserver`该如何连接? - - `rocketmq.name-server`支持配置多个`nameserver`地址,采用`;`分隔即可。例如:`172.19.0.1:9876;172.19.0.2:9876` - -1. `rocketMQTemplate`在什么时候被销毁? - - 开发者在项目中使用`rocketMQTemplate`发送消息时,不需要手动执行`rocketMQTemplate.destroy()`方法, `rocketMQTemplate`会在spring容器销毁时自动销毁。 - -1. 启动报错:`Caused by: org.apache.rocketmq.client.exception.MQClientException: The consumer group[xxx] has been created before, specify another name please` - - RocketMQ在设计时就不希望一个消费者同时处理多个类型的消息,因此同一个`consumerGroup`下的consumer职责应该是一样的,不要干不同的事情(即消费多个topic)。建议`consumerGroup`与`topic`一一对应。 - -1. 发送的消息内容体是如何被序列化与反序列化的? - - RocketMQ的消息体都是以`byte[]`方式存储。当业务系统的消息内容体如果是`java.lang.String`类型时,统一按照`utf-8`编码转成`byte[]`;如果业务系统的消息内容为非`java.lang.String`类型,则采用[jackson-databind](https://github.com/FasterXML/jackson-databind)序列化成`JSON`格式的字符串之后,再统一按照`utf-8`编码转成`byte[]`。 - -1. 如何指定topic的`tags`? - - RocketMQ的最佳实践中推荐:一个应用尽可能用一个Topic,消息子类型用`tags`来标识,`tags`可以由应用自由设置。 - 在使用`rocketMQTemplate`发送消息时,通过设置发送方法的`destination`参数来设置消息的目的地,`destination`的格式为`topicName:tagName`,`:`前面表示topic的名称,后面表示`tags`名称。 - - > 注意: - > - > `tags`从命名来看像是一个复数,但发送消息时,目的地只能指定一个topic下的一个`tag`,不能指定多个。 - -1. 发送消息时如何设置消息的`key`? - - 可以通过重载的`xxxSend(String destination, Message msg, ...)`方法来发送消息,指定`msg`的`headers`来完成。示例: - - ```java - Message message = MessageBuilder.withPayload(payload).setHeader(MessageConst.PROPERTY_KEYS, msgId).build(); - rocketMQTemplate.send("topic-test", message); - ``` - - 同理还可以根据上面的方式来设置消息的`FLAG`、`WAIT_STORE_MSG_OK`以及一些用户自定义的其它头信息。 - - > 注意: - > - > 在将Spring的Message转化为RocketMQ的Message时,为防止`header`信息与RocketMQ的系统属性冲突,在所有`header`的名称前面都统一添加了前缀`USERS_`。因此在消费时如果想获取自定义的消息头信息,请遍历头信息中以`USERS_`开头的key即可。 - -1. 消费消息时,除了获取消息`payload`外,还想获取RocketMQ消息的其它系统属性,需要怎么做? - - 消费者在实现`RocketMQListener`接口时,只需要起泛型为`MessageExt`即可,这样在`onMessage`方法将接收到RocketMQ原生的`MessageExt`消息。 - - ```java - @Slf4j - @Service - @RocketMQMessageListener(topic = "test-topic-1", consumerGroup = "my-consumer_test-topic-1") - public class MyConsumer2 implements RocketMQListener{ - public void onMessage(MessageExt messageExt) { - log.info("received messageExt: {}", messageExt); - } - } - ``` - -1. 如何指定消费者从哪开始消费消息,或开始消费的位置? - - 消费者默认开始消费的位置请参考:[RocketMQ FAQ](http://rocketmq.apache.org/docs/faq/)。 - 若想自定义消费者开始的消费位置,只需在消费者类添加一个`RocketMQPushConsumerLifecycleListener`接口的实现即可。 示例如下: - - ```java - @Slf4j - @Service - @RocketMQMessageListener(topic = "test-topic-1", consumerGroup = "my-consumer_test-topic-1") - public class MyConsumer1 implements RocketMQListener, RocketMQPushConsumerLifecycleListener { - @Override - public void onMessage(String message) { - log.info("received message: {}", message); - } - - @Override - public void prepareStart(final DefaultMQPushConsumer consumer) { - // set consumer consume message from now - consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_TIMESTAMP); - consumer.setConsumeTimestamp(UtilAll.timeMillisToHumanString3(System.currentTimeMillis())); - } - } - ``` - - 同理,任何关于`DefaultMQPushConsumer`的更多其它其它配置,都可以采用上述方式来完成。 - -1. 如何发送事务消息(即半消息支持分布式事务)? - 在客户端,首先用户需要实现RocketMQLocalTransactionListener接口,并在接口类上注解声明@RocketMQTransactionListener,实现确认和回查方法;然后再使用资源模板RocketMQTemplate, - 调用方法sendMessageInTransaction()来进行消息的发布。 注意:这个方法通过指定发送者组名与具体的声明了txProducerGroup的TransactionListener进行关联,您也可以不指定这个值,从而使用默认的事务发送者组。 - -1. 如何声明不同name-server或者其他特定的属性来定义非标的RocketMQTemplate? - ```java - // 第一步: 定义非标的RocketMQTemplate使用你需要的属性,注意,这里的'nameServer'属性必须要定义,并且其取值不能与全局配置属性'rocketmq.name-server'相同 - // 也可以定义其他属性,如果不定义,它们取全局的配置属性值或默认值。 - - // 这个RocketMQTemplate的Spring Bean名是'extRocketMQTemplate', 与所定义的类名相同(但首字母小写) - @ExtRocketMQTemplateConfiguration(nameServer="127.0.0.1:9876" - , ... // 定义其他属性,如果有必要。 - ) - public class ExtRocketMQTemplate extends RocketMQTemplate { - //类里面不需要做任何修改 - } - - - // 第二步: 使用这个非标RocketMQTemplate - @Resource(name = "extRocketMQTemplate") // 这里必须定义name属性来指向上具体的Spring Bean. - private RocketMQTemplate extRocketMQTemplate; - // 接下来就可以正常使用这个extRocketMQTemplate了. - - ``` - -1. MessageListener消费端,是否可以指定不同的name-server而不是使用全局定义的'rocketmq.name-server'属性值 ? - - ```java - @Service - @RocketMQMessageListener( - nameServer = "NEW-NAMESERVER-LIST", // 可以使用这个optional属性来指定不同的name-server - topic = "test-topic-1", - consumerGroup = "my-consumer_test-topic-1", - enableMsgTrace = true, - customizedTraceTopic = "my-trace-topic" - ) - public class MyNameServerConsumer implements RocketMQListener { - ... - } - ``` \ No newline at end of file diff --git a/pom.xml b/pom.xml index b5f533f2..7e7ecce5 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ org.apache.rocketmq rocketmq-spring-all - 2.0.3 + 2.2.1-SNAPSHOT pom Apache RocketMQ Spring Boot ${project.version} @@ -39,7 +39,7 @@ git@github.com:apache/rocketmq-spring.git scm:git:git@github.com:apache/rocketmq-spring.git scm:git:git@github.com:apache/rocketmq-spring.git - rocketmq-spring-all-2.0.3 + HEAD @@ -104,7 +104,7 @@ org.jacoco jacoco-maven-plugin - 0.7.8 + 0.8.6 default-prepare-agent diff --git a/rocketmq-spring-boot-parent/pom.xml b/rocketmq-spring-boot-parent/pom.xml index adc5878b..12e08076 100644 --- a/rocketmq-spring-boot-parent/pom.xml +++ b/rocketmq-spring-boot-parent/pom.xml @@ -22,7 +22,7 @@ org.apache.rocketmq rocketmq-spring-all - 2.0.3 + 2.2.1-SNAPSHOT ../pom.xml @@ -38,11 +38,12 @@ 2.0.5.RELEASE 5.1.0.RELEASE - 2.0.3 + 2.2.1-SNAPSHOT - 4.5.1 + 4.8.0 1.7.25 - 2.9.7 + 2.11.1 + 1.2.72 1.8 @ @@ -111,7 +112,7 @@ org.apache.rocketmq rocketmq-client - ${rocketmq-version} + ${rocketmq.version} org.slf4j @@ -123,7 +124,7 @@ org.apache.rocketmq rocketmq-acl - ${rocketmq-version} + ${rocketmq.version} @@ -161,6 +162,12 @@ jackson-databind ${jackson.version} + + + com.alibaba + fastjson + ${fastjson.version} + diff --git a/rocketmq-spring-boot-samples/README.md b/rocketmq-spring-boot-samples/README.md index d1f2b273..ae5c3836 100644 --- a/rocketmq-spring-boot-samples/README.md +++ b/rocketmq-spring-boot-samples/README.md @@ -1,7 +1,5 @@ # rocketmq-spring-boot-samples -[中文](./README_zh_CN.md) - [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) It's a demo project for how to use [rocketmq-spring-boot](https://github.com/apache/rocketmq-spring) diff --git a/rocketmq-spring-boot-samples/README_zh_CN.md b/rocketmq-spring-boot-samples/README_zh_CN.md deleted file mode 100644 index c8f27d1c..00000000 --- a/rocketmq-spring-boot-samples/README_zh_CN.md +++ /dev/null @@ -1,40 +0,0 @@ -# rocketmq-spring-boot-samples - -[English](./README.md) - -[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) - -这里是一个使用rocketmq-spring-boot-starter的例子。 [rocketmq-spring-boot](https://github.com/apache/rocketmq-spring) - - -## 在本地运行这个测试例子 - -1. 如上面注意项所述,需要开发者在本地build并安装rocketmq-spring-boot-starter - -2. 根据RocketMQ官网的quick-start来启动NameServer和Broker,并验证是否启动正确。注意: 测试期间不要停止Broker或者NameServer -http://rocketmq.apache.org/docs/quick-start/ - -3. 创建测试例子所需要的Topic -``` -cd YOUR_ROCKETMQ_HOME - -bash bin/mqadmin updateTopic -c DefaultCluster -t string-topic -bash bin/mqadmin updateTopic -c DefaultCluster -t order-paid-topic -bash bin/mqadmin updateTopic -c DefaultCluster -t message-ext-topic -bash bin/mqadmin updateTopic -c DefaultCluster -t spring-transaction-topic -``` - -4. 编译并运行测试例子 - -``` -# 打开一个终端窗口,编译并启动发送端 -cd rocketmq-produce-demo -mvn clean package -java -jar target/rocketmq-produce-demo-0.0.1-SNAPSHOT.jar - -# 打开另一个终端窗口,编译并启动消费端 -cd rocketmq-consume-demo -mvn clean package -java -jar target/rocketmq-consume-demo-0.0.1-SNAPSHOT.jar -``` -结合测试代码,观察窗口中消息的发送和接收情况 diff --git a/rocketmq-spring-boot-samples/pom.xml b/rocketmq-spring-boot-samples/pom.xml index e348e15c..e582ab14 100644 --- a/rocketmq-spring-boot-samples/pom.xml +++ b/rocketmq-spring-boot-samples/pom.xml @@ -24,7 +24,7 @@ org.apache.rocketmq rocketmq-spring-boot-samples pom - 0.0.1-SNAPSHOT + 2.1.2-SNAPSHOT RocketMQ Spring Boot Samples Samples for RocketMQ Spring Boot @@ -38,7 +38,9 @@ - 2.0.3 + 1.8 + 1.8 + 2.1.2-SNAPSHOT @@ -60,7 +62,6 @@ validate validate - ${disable.checks} src/main/resources style/rmq_checkstyle.xml UTF-8 diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/pom.xml b/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/pom.xml index 47fc2212..041ee964 100644 --- a/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/pom.xml +++ b/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/pom.xml @@ -23,7 +23,7 @@ org.apache.rocketmq rocketmq-spring-boot-samples - 0.0.1-SNAPSHOT + 2.1.2-SNAPSHOT rocketmq-consume-acl-demo diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ACLStringConsumer.java b/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ACLStringConsumer.java index fe99d389..d2d654f0 100644 --- a/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ACLStringConsumer.java +++ b/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ACLStringConsumer.java @@ -29,7 +29,7 @@ topic = "normal_topic_define_in_Aliware_MQ", consumerGroup = "group_define_in_Aliware_MQ" //accessKey = "AK" // It will read by `rocketmq.consumer.access-key` key - //secretKey = "SK" // It will read by `rocketmq.consumer.access-key` key + //secretKey = "SK" // It will read by `rocketmq.consumer.secret-key` key ) public class ACLStringConsumer implements RocketMQListener { @Override diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ConsumerACLApplication.java b/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ConsumerACLApplication.java index 3bf266b1..f3d75783 100644 --- a/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ConsumerACLApplication.java +++ b/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ConsumerACLApplication.java @@ -17,17 +17,32 @@ package org.apache.rocketmq.samples.springboot; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import javax.annotation.Resource; +import java.util.List; + /** * ConsumerApplication */ @SpringBootApplication -public class ConsumerACLApplication { +public class ConsumerACLApplication implements CommandLineRunner { + + @Resource + private RocketMQTemplate rocketMQTemplate; public static void main(String[] args) { SpringApplication.run(ConsumerACLApplication.class, args); } + + @Override + public void run(String... args) throws Exception { + ////This is an example of pull consumer with access-key and secret-key. + List messages = rocketMQTemplate.receive(String.class); + System.out.printf("receive from rocketMQTemplate, messages=%s %n", messages); + } } diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/resources/application.properties b/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/resources/application.properties index 7ca3c429..3fe6abbe 100644 --- a/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/resources/application.properties +++ b/rocketmq-spring-boot-samples/rocketmq-consume-acl-demo/src/main/resources/application.properties @@ -1,6 +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. + + spring.application.name=rocketmq-consume-acl-demo rocketmq.name-server=Endpoint_of_Aliware_MQ +rocketmq.consumer.group=my-group1 +rocketmq.consumer.topic=test rocketmq.topic=normal_topic_define_in_Aliware_MQ # properties used in application code diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/pom.xml b/rocketmq-spring-boot-samples/rocketmq-consume-demo/pom.xml index 4093170b..69a569b5 100644 --- a/rocketmq-spring-boot-samples/rocketmq-consume-demo/pom.xml +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/pom.xml @@ -23,7 +23,7 @@ org.apache.rocketmq rocketmq-spring-boot-samples - 0.0.1-SNAPSHOT + 2.1.2-SNAPSHOT rocketmq-consume-demo diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ConsumerApplication.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ConsumerApplication.java index e4fbc8c9..6435d478 100644 --- a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ConsumerApplication.java +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ConsumerApplication.java @@ -17,17 +17,39 @@ package org.apache.rocketmq.samples.springboot; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import javax.annotation.Resource; +import java.util.List; + /** * ConsumerApplication */ @SpringBootApplication -public class ConsumerApplication { +public class ConsumerApplication implements CommandLineRunner { + + @Resource + private RocketMQTemplate rocketMQTemplate; + + @Resource(name = "extRocketMQTemplate") + private RocketMQTemplate extRocketMQTemplate; public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } + + @Override + public void run(String... args) throws Exception { + //This is an example of pull consumer using rocketMQTemplate. + List messages = rocketMQTemplate.receive(String.class); + System.out.printf("receive from rocketMQTemplate, messages=%s %n", messages); + + //This is an example of pull consumer using extRocketMQTemplate. + messages = extRocketMQTemplate.receive(String.class); + System.out.printf("receive from extRocketMQTemplate, messages=%s %n", messages); + } } diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ExtRocketMQTemplate.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/ExtRocketMQTemplate.java new file mode 100644 index 00000000..5499ac1c --- /dev/null +++ b/rocketmq-spring-boot-samples/rocketmq-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.spring.annotation.ExtRocketMQConsumerConfiguration; +import org.apache.rocketmq.spring.core.RocketMQTemplate; + +@ExtRocketMQConsumerConfiguration(topic = "${demo.rocketmq.topic}", group = "string_consumer") +public class ExtRocketMQTemplate extends RocketMQTemplate { +} \ No newline at end of file diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ConsumerWithReplyBytes.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ConsumerWithReplyBytes.java new file mode 100644 index 00000000..ad79c84f --- /dev/null +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ConsumerWithReplyBytes.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.common.message.MessageExt; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQReplyListener; +import org.springframework.stereotype.Service; + +/** + * The consumer that replying bytes + */ +@Service +@RocketMQMessageListener(topic = "${demo.rocketmq.bytesRequestTopic}", consumerGroup = "${demo.rocketmq.bytesRequestConsumer}", selectorExpression = "${demo.rocketmq.tag}") +public class ConsumerWithReplyBytes implements RocketMQReplyListener { + + @Override + public byte[] onMessage(MessageExt message) { + System.out.printf("------- ConsumerWithReplyBytes received: %s \n", message); + return "reply message content".getBytes(); + } +} diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ConsumerWithReplyGeneric.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ConsumerWithReplyGeneric.java new file mode 100644 index 00000000..e17e6751 --- /dev/null +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ConsumerWithReplyGeneric.java @@ -0,0 +1,36 @@ +/* + * 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.samples.springboot.domain.ProductWithPayload; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQReplyListener; +import org.springframework.stereotype.Service; + +/** + * The consumer that replying generic type + */ +@Service +@RocketMQMessageListener(topic = "${demo.rocketmq.genericRequestTopic}", consumerGroup = "${demo.rocketmq.genericRequestConsumer}", selectorExpression = "${demo.rocketmq.tag}") +public class ConsumerWithReplyGeneric implements RocketMQReplyListener> { + @Override + public ProductWithPayload onMessage(String message) { + System.out.printf("------- ConsumerWithReplyGeneric received: %s \n", message); + return new ProductWithPayload("replyProductName", "product payload"); + } +} diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/MessageExtConsumer.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/MessageExtConsumer.java index 59ef2630..3fa0b31a 100644 --- a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/MessageExtConsumer.java +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/MessageExtConsumer.java @@ -30,7 +30,7 @@ * MessageExtConsumer, consume listener impl class. */ @Service -@RocketMQMessageListener(topic = "message-ext-topic", selectorExpression = "tag1", consumerGroup = "${spring.application.name}-message-ext-consumer") +@RocketMQMessageListener(topic = "${demo.rocketmq.msgExtTopic}", selectorExpression = "tag0||tag1", consumerGroup = "${spring.application.name}-message-ext-consumer") public class MessageExtConsumer implements RocketMQListener, RocketMQPushConsumerLifecycleListener { @Override public void onMessage(MessageExt message) { diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ObjectConsumerWithReplyUser.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ObjectConsumerWithReplyUser.java new file mode 100644 index 00000000..e36fd665 --- /dev/null +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/ObjectConsumerWithReplyUser.java @@ -0,0 +1,40 @@ +/* + * 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.samples.springboot.domain.User; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQReplyListener; +import org.springframework.stereotype.Service; + +/** + * The consumer that replying Object + */ +@Service +@RocketMQMessageListener(topic = "${demo.rocketmq.objectRequestTopic}", consumerGroup = "${demo.rocketmq.objectRequestConsumer}", selectorExpression = "${demo.rocketmq.tag}") +public class ObjectConsumerWithReplyUser implements RocketMQReplyListener { + + @Override + public User onMessage(User user) { + System.out.printf("------- ObjectConsumerWithReplyUser received: %s \n", user); + User replyUser = new User(); + replyUser.setUserAge((byte) 10); + replyUser.setUserName("replyUserName"); + return replyUser; + } +} diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/OrderPaidEventConsumer.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/OrderPaidEventConsumer.java index 677e0eab..04cb17ff 100644 --- a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/OrderPaidEventConsumer.java +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/OrderPaidEventConsumer.java @@ -31,6 +31,6 @@ public class OrderPaidEventConsumer implements RocketMQListener @Override public void onMessage(OrderPaidEvent orderPaidEvent) { - System.out.printf("------- OrderPaidEventConsumer received: %s \n", orderPaidEvent); + System.out.printf("------- OrderPaidEventConsumer received: %s [orderId : %s]\n", orderPaidEvent,orderPaidEvent.getOrderId()); } } diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/StringConsumer.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/StringConsumer.java index 2b90c69d..11ac4890 100644 --- a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/StringConsumer.java +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/StringConsumer.java @@ -22,10 +22,10 @@ import org.springframework.stereotype.Service; /** - * RocketMQMessageListener + * StringConsumer */ @Service -@RocketMQMessageListener(topic = "${demo.rocketmq.topic}", consumerGroup = "string_consumer") +@RocketMQMessageListener(topic = "${demo.rocketmq.topic}", consumerGroup = "string_consumer", selectorExpression = "${demo.rocketmq.tag}") public class StringConsumer implements RocketMQListener { @Override public void onMessage(String message) { diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/StringConsumerNewNS.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/StringConsumerNewNS.java index 5d2c8727..4c7dfc09 100644 --- a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/StringConsumerNewNS.java +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/StringConsumerNewNS.java @@ -25,7 +25,7 @@ * RocketMQMessageListener */ @Service -@RocketMQMessageListener(nameServer = "${demo.rocketmq.myNameServer}", topic = "${demo.rocketmq.topic}", consumerGroup = "string_consumer") +@RocketMQMessageListener(nameServer = "${demo.rocketmq.myNameServer}", topic = "${demo.rocketmq.topic}", consumerGroup = "string_consumer_newns") public class StringConsumerNewNS implements RocketMQListener { @Override public void onMessage(String message) { diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/StringConsumerWithReplyString.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/StringConsumerWithReplyString.java new file mode 100644 index 00000000..a57f20db --- /dev/null +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/StringConsumerWithReplyString.java @@ -0,0 +1,36 @@ +/* + * 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.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQReplyListener; +import org.springframework.stereotype.Service; + +/** + * The consumer that replying String + */ +@Service +@RocketMQMessageListener(topic = "${demo.rocketmq.stringRequestTopic}", consumerGroup = "${demo.rocketmq.stringRequestConsumer}", selectorExpression = "${demo.rocketmq.tag}") +public class StringConsumerWithReplyString implements RocketMQReplyListener { + + @Override + public String onMessage(String message) { + System.out.printf("------- StringConsumerWithReplyString received: %s \n", message); + return "reply string"; + } +} diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/UserConsumer.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/UserConsumer.java new file mode 100644 index 00000000..b9e36475 --- /dev/null +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/consumer/UserConsumer.java @@ -0,0 +1,36 @@ +/* + * 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.samples.springboot.domain.User; +import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.core.RocketMQListener; +import org.springframework.stereotype.Service; + +/** + * RocketMQMessageListener + */ +@Service +@RocketMQMessageListener(nameServer = "${demo.rocketmq.myNameServer}", topic = "${demo.rocketmq.topic.user}", consumerGroup = "user_consumer") +public class UserConsumer implements RocketMQListener { + @Override + public void onMessage(User message) { + + System.out.printf("######## user_consumer received: %s ; age: %s ; name: %s \n", message, message.getUserAge(), message.getUserName()); + } +} diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/ProductWithPayload.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/ProductWithPayload.java new file mode 100644 index 00000000..e2419299 --- /dev/null +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/ProductWithPayload.java @@ -0,0 +1,54 @@ +/* + * 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 ProductWithPayload { + private String productName; + private T payload; + + public ProductWithPayload() { + } + + public ProductWithPayload(String productName, T payload) { + this.productName = productName; + this.payload = payload; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public T getPayload() { + return payload; + } + + public void setPayload(T payload) { + this.payload = payload; + } + + @Override public String toString() { + return "ProductWithPayload{" + + "productName='" + productName + '\'' + + ", payload=" + payload + + '}'; + } +} \ No newline at end of file diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/User.java b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/User.java new file mode 100644 index 00000000..4f2579f6 --- /dev/null +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/User.java @@ -0,0 +1,49 @@ +/* + * 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 User { + private String userName; + private Byte userAge; + + public String getUserName() { + return userName; + } + + public User setUserName(String userName) { + this.userName = userName; + return this; + } + + public Byte getUserAge() { + return userAge; + } + + public User setUserAge(Byte userAge) { + this.userAge = userAge; + return this; + } + + @Override + public String toString() { + return "User{" + + "userName='" + userName + '\'' + + ", userAge=" + userAge + + '}'; + } +} \ No newline at end of file diff --git a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/resources/application.properties b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/resources/application.properties index 404cb102..5953e487 100644 --- a/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/resources/application.properties +++ b/rocketmq-spring-boot-samples/rocketmq-consume-demo/src/main/resources/application.properties @@ -1,12 +1,38 @@ +# 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. + spring.application.name=rocketmq-consume-demo rocketmq.name-server=localhost:9876 - +rocketmq.consumer.group=my-group1 +rocketmq.consumer.topic=test # properties used in application code demo.rocketmq.topic=string-topic +demo.rocketmq.bytesRequestTopic=bytesRequestTopic +demo.rocketmq.stringRequestTopic=stringRequestTopic +demo.rocketmq.objectRequestTopic=objectRequestTopic +demo.rocketmq.genericRequestTopic=genericRequestTopic +demo.rocketmq.bytesRequestConsumer=bytesRequestConsumer +demo.rocketmq.stringRequestConsumer=stringRequestConsumer +demo.rocketmq.objectRequestConsumer=objectRequestConsumer +demo.rocketmq.genericRequestConsumer=genericRequestConsumer demo.rocketmq.orderTopic=order-paid-topic demo.rocketmq.msgExtTopic=message-ext-topic demo.rocketmq.transTopic=spring-transaction-topic +demo.rocketmq.topic.user=user-topic +demo.rocketmq.tag=tagA # another nameserver different global demo.rocketmq.myNameServer=127.0.0.1:9876 \ No newline at end of file diff --git a/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/pom.xml b/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/pom.xml index 9193d986..08ebc6e9 100644 --- a/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/pom.xml +++ b/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/pom.xml @@ -23,7 +23,7 @@ org.apache.rocketmq rocketmq-spring-boot-samples - 0.0.1-SNAPSHOT + 2.1.2-SNAPSHOT rocketmq-produce-acl-demo diff --git a/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ProducerACLApplication.java b/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ProducerACLApplication.java index 1c403545..486a4138 100644 --- a/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ProducerACLApplication.java +++ b/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/src/main/java/org/apache/rocketmq/samples/springboot/ProducerACLApplication.java @@ -41,7 +41,6 @@ */ @SpringBootApplication public class ProducerACLApplication implements CommandLineRunner { - private static final String TX_PGROUP_NAME = "myTxProducerGroup"; @Resource private RocketMQTemplate rocketMQTemplate; @Value("${demo.rocketmq.transTopic}") @@ -74,8 +73,8 @@ private void testTransaction() throws MessagingException { try { Message msg = MessageBuilder.withPayload("Hello RocketMQ " + i). - setHeader(RocketMQHeaders.KEYS, "KEY_" + i).build(); - SendResult sendResult = rocketMQTemplate.sendMessageInTransaction(TX_PGROUP_NAME, + setHeader(RocketMQHeaders.TRANSACTION_ID, "KEY_" + i).build(); + SendResult sendResult = rocketMQTemplate.sendMessageInTransaction( springTransTopic + ":" + tags[i % tags.length], msg, null); System.out.printf("------ send Transactional msg body = %s , sendResult=%s %n", msg.getPayload(), sendResult.getSendStatus()); @@ -87,11 +86,7 @@ private void testTransaction() throws MessagingException { } } - @RocketMQTransactionListener( - txProducerGroup = TX_PGROUP_NAME, - accessKey = "AK", // if not setting, it will read by `rocketmq.producer.access-key` key - secretKey = "SK" // if not setting, it will read by `rocketmq.producer.secret-key` key - ) + @RocketMQTransactionListener class TransactionListenerImpl implements RocketMQLocalTransactionListener { private AtomicInteger transactionIndex = new AtomicInteger(0); diff --git a/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/src/main/resources/application.properties b/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/src/main/resources/application.properties index dd49d0dc..08722805 100644 --- a/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/src/main/resources/application.properties +++ b/rocketmq-spring-boot-samples/rocketmq-produce-acl-demo/src/main/resources/application.properties @@ -1,3 +1,18 @@ +# 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. + rocketmq.name-server=Endpoint_of_Aliware_MQ rocketmq.producer.group=my-group1 rocketmq.producer.access-key=AK diff --git a/rocketmq-spring-boot-samples/rocketmq-produce-demo/pom.xml b/rocketmq-spring-boot-samples/rocketmq-produce-demo/pom.xml index 0ef364d1..f888b14a 100644 --- a/rocketmq-spring-boot-samples/rocketmq-produce-demo/pom.xml +++ b/rocketmq-spring-boot-samples/rocketmq-produce-demo/pom.xml @@ -23,7 +23,7 @@ org.apache.rocketmq rocketmq-spring-boot-samples - 0.0.1-SNAPSHOT + 2.1.2-SNAPSHOT rocketmq-produce-demo diff --git a/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/java/org/apache/rocketmq/samples/springboot/ProducerApplication.java b/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/java/org/apache/rocketmq/samples/springboot/ProducerApplication.java index 9e49170e..f8140d41 100644 --- a/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/java/org/apache/rocketmq/samples/springboot/ProducerApplication.java +++ b/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/java/org/apache/rocketmq/samples/springboot/ProducerApplication.java @@ -17,10 +17,20 @@ package org.apache.rocketmq.samples.springboot; +import com.alibaba.fastjson.TypeReference; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.Resource; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.samples.springboot.domain.OrderPaidEvent; +import org.apache.rocketmq.samples.springboot.domain.ProductWithPayload; +import org.apache.rocketmq.samples.springboot.domain.User; import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener; +import org.apache.rocketmq.spring.core.RocketMQLocalRequestCallback; import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener; import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState; import org.apache.rocketmq.spring.core.RocketMQTemplate; @@ -30,32 +40,38 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.MessagingException; import org.springframework.messaging.support.MessageBuilder; - -import javax.annotation.Resource; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; +import org.springframework.util.MimeTypeUtils; /** * Producer, using RocketMQTemplate sends a variety of messages */ @SpringBootApplication public class ProducerApplication implements CommandLineRunner { - private static final String TX_PGROUP_NAME = "myTxProducerGroup"; @Resource private RocketMQTemplate rocketMQTemplate; @Value("${demo.rocketmq.transTopic}") private String springTransTopic; @Value("${demo.rocketmq.topic}") private String springTopic; + @Value("${demo.rocketmq.topic.user}") + private String userTopic; + @Value("${demo.rocketmq.orderTopic}") private String orderPaidTopic; @Value("${demo.rocketmq.msgExtTopic}") private String msgExtTopic; + @Value("${demo.rocketmq.stringRequestTopic}") + private String stringRequestTopic; + @Value("${demo.rocketmq.bytesRequestTopic}") + private String bytesRequestTopic; + @Value("${demo.rocketmq.objectRequestTopic}") + private String objectRequestTopic; + @Value("${demo.rocketmq.genericRequestTopic}") + private String genericRequestTopic; + @Resource(name = "extRocketMQTemplate") private RocketMQTemplate extRocketMQTemplate; @@ -69,8 +85,15 @@ public void run(String... args) throws Exception { SendResult sendResult = rocketMQTemplate.syncSend(springTopic, "Hello, World!"); System.out.printf("syncSend1 to topic %s sendResult=%s %n", springTopic, sendResult); + sendResult = rocketMQTemplate.syncSend(userTopic, new User().setUserAge((byte) 18).setUserName("Kitty")); + System.out.printf("syncSend1 to topic %s sendResult=%s %n", userTopic, sendResult); + + sendResult = rocketMQTemplate.syncSend(userTopic, MessageBuilder.withPayload( + new User().setUserAge((byte) 21).setUserName("Lester")).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON_VALUE).build()); + System.out.printf("syncSend1 to topic %s sendResult=%s %n", userTopic, sendResult); + // Use the extRocketMQTemplate - sendResult = extRocketMQTemplate.syncSend(springTopic, "Hello, World!"); + sendResult = extRocketMQTemplate.syncSend(springTopic, MessageBuilder.withPayload("Hello, World!2222".getBytes()).build()); System.out.printf("extRocketMQTemplate.syncSend1 to topic %s sendResult=%s %n", springTopic, sendResult); // Send string with spring Message @@ -79,10 +102,12 @@ public void run(String... args) throws Exception { // Send user-defined object rocketMQTemplate.asyncSend(orderPaidTopic, new OrderPaidEvent("T_001", new BigDecimal("88.00")), new SendCallback() { + @Override public void onSuccess(SendResult var1) { System.out.printf("async onSucess SendResult=%s %n", var1); } + @Override public void onException(Throwable var1) { System.out.printf("async onException Throwable=%s %n", var1); } @@ -95,19 +120,60 @@ public void onException(Throwable var1) { rocketMQTemplate.convertAndSend(msgExtTopic + ":tag1", "I'm from tag1"); System.out.printf("syncSend topic %s tag %s %n", msgExtTopic, "tag1"); - // Send a batch of strings testBatchMessages(); - // Send transactional messages - testTransaction(); + // Send transactional messages using rocketMQTemplate + testRocketMQTemplateTransaction(); + + // Send transactional messages using extRocketMQTemplate + testExtRocketMQTemplateTransaction(); + + // Send request in sync mode and receive a reply of String type. + String replyString = rocketMQTemplate.sendAndReceive(stringRequestTopic, "request string", String.class); + System.out.printf("send %s and receive %s %n", "request string", replyString); + + // Send request in sync mode with timeout parameter and receive a reply of byte[] type. + byte[] replyBytes = rocketMQTemplate.sendAndReceive(bytesRequestTopic, MessageBuilder.withPayload("request byte[]").build(), byte[].class, 3000); + System.out.printf("send %s and receive %s %n", "request byte[]", new String(replyBytes)); + + // Send request in sync mode with hashKey parameter and receive a reply of User type. + User requestUser = new User().setUserAge((byte) 9).setUserName("requestUserName"); + User replyUser = rocketMQTemplate.sendAndReceive(objectRequestTopic, requestUser, User.class, "order-id"); + System.out.printf("send %s and receive %s %n", requestUser, replyUser); + // Send request in sync mode with timeout and delayLevel parameter parameter and receive a reply of generic type. + ProductWithPayload replyGenericObject = rocketMQTemplate.sendAndReceive(genericRequestTopic, "request generic", + new TypeReference>() { + }.getType(), 30000, 2); + System.out.printf("send %s and receive %s %n", "request generic", replyGenericObject); + + // Send request in async mode and receive a reply of String type. + rocketMQTemplate.sendAndReceive(stringRequestTopic, "request string", new RocketMQLocalRequestCallback() { + @Override public void onSuccess(String message) { + System.out.printf("send %s and receive %s %n", "request string", message); + } + + @Override public void onException(Throwable e) { + e.printStackTrace(); + } + }); + // Send request in async mode and receive a reply of User type. + rocketMQTemplate.sendAndReceive(objectRequestTopic, new User().setUserAge((byte) 9).setUserName("requestUserName"), new RocketMQLocalRequestCallback() { + @Override public void onSuccess(User message) { + System.out.printf("send user object and receive %s %n", message.toString()); + } + + @Override public void onException(Throwable e) { + e.printStackTrace(); + } + }, 5000); } private void testBatchMessages() { List msgs = new ArrayList(); for (int i = 0; i < 10; i++) { msgs.add(MessageBuilder.withPayload("Hello RocketMQ Batch Msg#" + i). - setHeader(RocketMQHeaders.KEYS, "KEY_" + i).build()); + setHeader(RocketMQHeaders.KEYS, "KEY_" + i).build()); } SendResult sr = rocketMQTemplate.syncSend(springTopic, msgs, 60000); @@ -115,17 +181,16 @@ private void testBatchMessages() { System.out.printf("--- Batch messages send result :" + sr); } - - private void testTransaction() throws MessagingException { - String[] tags = new String[]{"TagA", "TagB", "TagC", "TagD", "TagE"}; + private void testRocketMQTemplateTransaction() throws MessagingException { + String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"}; for (int i = 0; i < 10; i++) { try { - Message msg = MessageBuilder.withPayload("Hello RocketMQ " + i). - setHeader(RocketMQHeaders.KEYS, "KEY_" + i).build(); - SendResult sendResult = rocketMQTemplate.sendMessageInTransaction(TX_PGROUP_NAME, + Message msg = MessageBuilder.withPayload("rocketMQTemplate transactional message " + i). + setHeader(RocketMQHeaders.TRANSACTION_ID, "KEY_" + i).build(); + SendResult sendResult = rocketMQTemplate.sendMessageInTransaction( springTransTopic + ":" + tags[i % tags.length], msg, null); - System.out.printf("------ send Transactional msg body = %s , sendResult=%s %n", + System.out.printf("------rocketMQTemplate send Transactional msg body = %s , sendResult=%s %n", msg.getPayload(), sendResult.getSendStatus()); Thread.sleep(10); @@ -135,7 +200,24 @@ private void testTransaction() throws MessagingException { } } - @RocketMQTransactionListener(txProducerGroup = TX_PGROUP_NAME) + private void testExtRocketMQTemplateTransaction() throws MessagingException { + for (int i = 0; i < 10; i++) { + try { + Message msg = MessageBuilder.withPayload("extRocketMQTemplate transactional message " + i). + setHeader(RocketMQHeaders.TRANSACTION_ID, "KEY_" + i).build(); + SendResult sendResult = extRocketMQTemplate.sendMessageInTransaction( + springTransTopic, msg, null); + System.out.printf("------ExtRocketMQTemplate send Transactional msg body = %s , sendResult=%s %n", + msg.getPayload(), sendResult.getSendStatus()); + + Thread.sleep(10); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @RocketMQTransactionListener class TransactionListenerImpl implements RocketMQLocalTransactionListener { private AtomicInteger transactionIndex = new AtomicInteger(0); @@ -143,7 +225,7 @@ class TransactionListenerImpl implements RocketMQLocalTransactionListener { @Override public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) { - String transId = (String)msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID); + String transId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID); System.out.printf("#### executeLocalTransaction is executed, msgTransactionId=%s %n", transId); int value = transactionIndex.getAndIncrement(); @@ -169,19 +251,19 @@ public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object @Override public RocketMQLocalTransactionState checkLocalTransaction(Message msg) { - String transId = (String)msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID); + String transId = (String) msg.getHeaders().get(RocketMQHeaders.TRANSACTION_ID); RocketMQLocalTransactionState retState = RocketMQLocalTransactionState.COMMIT; Integer status = localTrans.get(transId); if (null != status) { switch (status) { case 0: - retState = RocketMQLocalTransactionState.UNKNOWN; + retState = RocketMQLocalTransactionState.COMMIT; break; case 1: - retState = RocketMQLocalTransactionState.COMMIT; + retState = RocketMQLocalTransactionState.ROLLBACK; break; case 2: - retState = RocketMQLocalTransactionState.ROLLBACK; + retState = RocketMQLocalTransactionState.UNKNOWN; break; } } @@ -192,4 +274,18 @@ public RocketMQLocalTransactionState checkLocalTransaction(Message msg) { } } + @RocketMQTransactionListener(rocketMQTemplateBeanName = "extRocketMQTemplate") + class ExtTransactionListenerImpl implements RocketMQLocalTransactionListener { + @Override + public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) { + System.out.printf("ExtTransactionListenerImpl executeLocalTransaction and return UNKNOWN. \n"); + return RocketMQLocalTransactionState.UNKNOWN; + } + + @Override + public RocketMQLocalTransactionState checkLocalTransaction(Message msg) { + System.out.printf("ExtTransactionListenerImpl checkLocalTransaction and return COMMIT. \n"); + return RocketMQLocalTransactionState.COMMIT; + } + } } diff --git a/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/ProductWithPayload.java b/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/ProductWithPayload.java new file mode 100644 index 00000000..e2419299 --- /dev/null +++ b/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/ProductWithPayload.java @@ -0,0 +1,54 @@ +/* + * 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 ProductWithPayload { + private String productName; + private T payload; + + public ProductWithPayload() { + } + + public ProductWithPayload(String productName, T payload) { + this.productName = productName; + this.payload = payload; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public T getPayload() { + return payload; + } + + public void setPayload(T payload) { + this.payload = payload; + } + + @Override public String toString() { + return "ProductWithPayload{" + + "productName='" + productName + '\'' + + ", payload=" + payload + + '}'; + } +} \ No newline at end of file diff --git a/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/User.java b/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/User.java new file mode 100644 index 00000000..4f2579f6 --- /dev/null +++ b/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/java/org/apache/rocketmq/samples/springboot/domain/User.java @@ -0,0 +1,49 @@ +/* + * 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 User { + private String userName; + private Byte userAge; + + public String getUserName() { + return userName; + } + + public User setUserName(String userName) { + this.userName = userName; + return this; + } + + public Byte getUserAge() { + return userAge; + } + + public User setUserAge(Byte userAge) { + this.userAge = userAge; + return this; + } + + @Override + public String toString() { + return "User{" + + "userName='" + userName + '\'' + + ", userAge=" + userAge + + '}'; + } +} \ No newline at end of file diff --git a/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/resources/application.properties b/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/resources/application.properties index 3a685051..7b5e4228 100644 --- a/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/resources/application.properties +++ b/rocketmq-spring-boot-samples/rocketmq-produce-demo/src/main/resources/application.properties @@ -1,3 +1,18 @@ +# 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. + rocketmq.name-server=localhost:9876 rocketmq.producer.group=my-group1 rocketmq.producer.sendMessageTimeout=300000 @@ -7,5 +22,11 @@ demo.rocketmq.topic=string-topic demo.rocketmq.orderTopic=order-paid-topic demo.rocketmq.msgExtTopic=message-ext-topic demo.rocketmq.transTopic=spring-transaction-topic +demo.rocketmq.topic.user=user-topic + +demo.rocketmq.bytesRequestTopic=bytesRequestTopic:tagA +demo.rocketmq.stringRequestTopic=stringRequestTopic:tagA +demo.rocketmq.objectRequestTopic=objectRequestTopic:tagA +demo.rocketmq.genericRequestTopic=genericRequestTopic:tagA demo.rocketmq.extNameServer=127.0.0.1:9876 \ No newline at end of file diff --git a/rocketmq-spring-boot-starter/pom.xml b/rocketmq-spring-boot-starter/pom.xml index 3ec565a9..370d2262 100644 --- a/rocketmq-spring-boot-starter/pom.xml +++ b/rocketmq-spring-boot-starter/pom.xml @@ -22,7 +22,7 @@ org.apache.rocketmq rocketmq-spring-boot-parent - 2.0.3 + 2.2.1-SNAPSHOT ../rocketmq-spring-boot-parent/pom.xml @@ -30,7 +30,7 @@ jar RocketMQ Spring Boot Starter - SRocketMQ Spring Boot Starter + RocketMQ Spring Boot Starter https://github.com/apache/rocketmq-spring diff --git a/rocketmq-spring-boot/pom.xml b/rocketmq-spring-boot/pom.xml index b93e05cb..3426246b 100644 --- a/rocketmq-spring-boot/pom.xml +++ b/rocketmq-spring-boot/pom.xml @@ -22,7 +22,7 @@ org.apache.rocketmq rocketmq-spring-boot-parent - 2.0.3 + 2.2.1-SNAPSHOT ../rocketmq-spring-boot-parent/pom.xml @@ -30,7 +30,7 @@ jar RocketMQ Spring Boot AutoConfigure - SRocketMQ Spring Boot AutoConfigure + RocketMQ Spring Boot AutoConfigure https://github.com/apache/rocketmq-spring diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/ExtRocketMQConsumerConfiguration.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/ExtRocketMQConsumerConfiguration.java new file mode 100644 index 00000000..63e4e371 --- /dev/null +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/ExtRocketMQConsumerConfiguration.java @@ -0,0 +1,97 @@ +/* + * 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.spring.annotation; + +import org.springframework.stereotype.Component; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface ExtRocketMQConsumerConfiguration { + + String NAME_SERVER_PLACEHOLDER = "${rocketmq.name-server:}"; + String GROUP_PLACEHOLDER = "${rocketmq.consumer.group:}"; + String TOPIC_PLACEHOLDER = "${rocketmq.consumer.topic:}"; + String ACCESS_CHANNEL_PLACEHOLDER = "${rocketmq.access-channel:}"; + String ACCESS_KEY_PLACEHOLDER = "${rocketmq.consumer.access-key:}"; + String SECRET_KEY_PLACEHOLDER = "${rocketmq.consumer.secret-key:}"; + + /** + * The component name of the Producer configuration. + */ + String value() default ""; + + /** + * The property of "name-server". + */ + String nameServer() default NAME_SERVER_PLACEHOLDER; + + /** + * The property of "access-channel". + */ + String accessChannel() default ACCESS_CHANNEL_PLACEHOLDER; + + /** + * Group name of consumer. + */ + String group() default GROUP_PLACEHOLDER; + + /** + * Topic name of consumer. + */ + String topic() default TOPIC_PLACEHOLDER; + + /** + * Control message mode, if you want all subscribers receive message all message, broadcasting is a good choice. + */ + MessageModel messageModel() default MessageModel.CLUSTERING; + + /** + * Control how to selector message. + * + * @see SelectorType + */ + SelectorType selectorType() default SelectorType.TAG; + + /** + * Control which message can be select. Grammar please see {@link SelectorType#TAG} and {@link SelectorType#SQL92} + */ + String selectorExpression() default "*"; + + /** + * The property of "access-key". + */ + String accessKey() default ACCESS_KEY_PLACEHOLDER; + + /** + * The property of "secret-key". + */ + String secretKey() default SECRET_KEY_PLACEHOLDER; + + /** + * Maximum number of messages pulled each time. + */ + int pullBatchSize() default 10; +} \ No newline at end of file diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/ExtRocketMQTemplateConfiguration.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/ExtRocketMQTemplateConfiguration.java index c268b571..05eaa466 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/ExtRocketMQTemplateConfiguration.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/ExtRocketMQTemplateConfiguration.java @@ -17,13 +17,12 @@ package org.apache.rocketmq.spring.annotation; -import org.springframework.stereotype.Component; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.stereotype.Component; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @@ -38,7 +37,7 @@ /** * The property of "name-server". */ - String nameServer(); + String nameServer() default "${rocketmq.name-server:}"; /** * Name of producer. diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/RocketMQMessageListener.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/RocketMQMessageListener.java index f1c77485..7b7ff9ad 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/RocketMQMessageListener.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/RocketMQMessageListener.java @@ -76,9 +76,9 @@ int consumeThreadMax() default 64; /** - * Max consumer timeout, default 30s. + * Maximum amount of time in minutes a message may block the consuming thread. */ - long consumeTimeout() default 30000L; + long consumeTimeout() default 15L; /** * The property of "access-key". diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/RocketMQTransactionListener.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/RocketMQTransactionListener.java index f3f874cf..0faa57a0 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/RocketMQTransactionListener.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/annotation/RocketMQTransactionListener.java @@ -17,18 +17,17 @@ package org.apache.rocketmq.spring.annotation; -import org.apache.rocketmq.spring.config.RocketMQConfigUtils; -import org.springframework.stereotype.Component; - import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.stereotype.Component; /** * This annotation is used over a class which implements interface - * org.apache.rocketmq.client.producer.TransactionListener. The class implements + * org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener, which will be converted to + * org.apache.rocketmq.client.producer.TransactionListener later. The class implements * two methods for process callback events after the txProducer sends a transactional message. *

Note: The annotation is used only on RocketMQ client producer side, it can not be used * on consumer side. @@ -39,14 +38,6 @@ @Component public @interface RocketMQTransactionListener { - /** - * Declare the txProducerGroup that is used to relate callback event to the listener, rocketMQTemplate must send a - * transactional message with the declared txProducerGroup. - *

- *

It is suggested to use the default txProducerGroup if your system only needs to define a TransactionListener class. - */ - String txProducerGroup() default RocketMQConfigUtils.ROCKETMQ_TRANSACTION_DEFAULT_GLOBAL_NAME; - /** * Set ExecutorService params -- corePoolSize */ @@ -68,12 +59,9 @@ int blockingQueueSize() default 2000; /** - * The property of "access-key" + * Set rocketMQTemplate bean name, the default is rocketMQTemplate. + * if use ExtRocketMQTemplate, can set ExtRocketMQTemplate bean name. */ - String accessKey() default "${rocketmq.producer.access-key}"; + String rocketMQTemplateBeanName() default "rocketMQTemplate"; - /** - * The property of "secret-key" - */ - String secretKey() default "${rocketmq.producer.secret-key}"; } diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/ExtConsumerResetConfiguration.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/ExtConsumerResetConfiguration.java new file mode 100644 index 00000000..7e81b5bb --- /dev/null +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/ExtConsumerResetConfiguration.java @@ -0,0 +1,149 @@ +/* + * 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.spring.autoconfigure; + +import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; +import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.spring.annotation.ExtRocketMQConsumerConfiguration; +import org.apache.rocketmq.spring.annotation.MessageModel; +import org.apache.rocketmq.spring.annotation.SelectorType; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.apache.rocketmq.spring.support.RocketMQMessageConverter; +import org.apache.rocketmq.spring.support.RocketMQUtil; +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.StandardEnvironment; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +import java.util.Map; +import java.util.stream.Collectors; + +@Configuration +public class ExtConsumerResetConfiguration implements ApplicationContextAware, SmartInitializingSingleton { + + private final static Logger log = LoggerFactory.getLogger(ExtConsumerResetConfiguration.class); + + private ConfigurableApplicationContext applicationContext; + + private StandardEnvironment environment; + + private RocketMQProperties rocketMQProperties; + + private RocketMQMessageConverter rocketMQMessageConverter; + + public ExtConsumerResetConfiguration(RocketMQMessageConverter rocketMQMessageConverter, + StandardEnvironment 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(ExtRocketMQConsumerConfiguration.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 (!RocketMQTemplate.class.isAssignableFrom(bean.getClass())) { + throw new IllegalStateException(clazz + " is not instance of " + RocketMQTemplate.class.getName()); + } + + ExtRocketMQConsumerConfiguration annotation = clazz.getAnnotation(ExtRocketMQConsumerConfiguration.class); + GenericApplicationContext genericApplicationContext = (GenericApplicationContext) applicationContext; + validate(annotation, genericApplicationContext); + + DefaultLitePullConsumer consumer = null; + try { + consumer = createConsumer(annotation); + // Set instanceName same as the beanName + consumer.setInstanceName(beanName); + consumer.start(); + } catch (Exception e) { + log.error("Failed to startup PullConsumer for RocketMQTemplate {}", beanName, e); + } + RocketMQTemplate rocketMQTemplate = (RocketMQTemplate) bean; + rocketMQTemplate.setConsumer(consumer); + rocketMQTemplate.setMessageConverter(rocketMQMessageConverter.getMessageConverter()); + log.info("Set real consumer to :{} {}", beanName, annotation.value()); + } + + private DefaultLitePullConsumer createConsumer(ExtRocketMQConsumerConfiguration annotation) + throws MQClientException { + + RocketMQProperties.Consumer consumerConfig = rocketMQProperties.getConsumer(); + if (consumerConfig == null) { + consumerConfig = new RocketMQProperties.Consumer(); + } + String nameServer = resolvePlaceholders(annotation.nameServer(), rocketMQProperties.getNameServer()); + String groupName = resolvePlaceholders(annotation.group(), consumerConfig.getGroup()); + String topicName = resolvePlaceholders(annotation.topic(), consumerConfig.getTopic()); + Assert.hasText(nameServer, "[nameServer] must not be null"); + Assert.hasText(groupName, "[group] must not be null"); + Assert.hasText(topicName, "[topic] must not be null"); + + String accessChannel = resolvePlaceholders(annotation.accessChannel(), rocketMQProperties.getAccessChannel()); + MessageModel messageModel = annotation.messageModel(); + SelectorType selectorType = annotation.selectorType(); + String selectorExpression = annotation.selectorExpression(); + String ak = resolvePlaceholders(annotation.accessKey(), consumerConfig.getAccessKey()); + String sk = resolvePlaceholders(annotation.secretKey(), consumerConfig.getSecretKey()); + int pullBatchSize = annotation.pullBatchSize(); + + DefaultLitePullConsumer litePullConsumer = RocketMQUtil.createDefaultLitePullConsumer(nameServer, accessChannel, + groupName, topicName, messageModel, selectorType, selectorExpression, ak, sk, pullBatchSize); + return litePullConsumer; + } + + private String resolvePlaceholders(String text, String defaultValue) { + String value = environment.resolvePlaceholders(text); + return StringUtils.isEmpty(value) ? defaultValue : value; + } + + private void validate(ExtRocketMQConsumerConfiguration 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())); + } + } +} \ No newline at end of file diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/ExtProducerResetConfiguration.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/ExtProducerResetConfiguration.java index 192bfc98..58d5eae0 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/ExtProducerResetConfiguration.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/ExtProducerResetConfiguration.java @@ -17,16 +17,18 @@ package org.apache.rocketmq.spring.autoconfigure; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.rocketmq.acl.common.AclClientRPCHook; -import org.apache.rocketmq.acl.common.SessionCredentials; +import java.util.Map; +import java.util.stream.Collectors; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.spring.annotation.ExtRocketMQTemplateConfiguration; import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.apache.rocketmq.spring.support.RocketMQMessageConverter; +import org.apache.rocketmq.spring.support.RocketMQUtil; 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; @@ -38,10 +40,6 @@ import org.springframework.core.env.StandardEnvironment; import org.springframework.util.StringUtils; -import java.util.Map; -import java.util.Objects; - - @Configuration public class ExtProducerResetConfiguration implements ApplicationContextAware, SmartInitializingSingleton { private final static Logger log = LoggerFactory.getLogger(ExtProducerResetConfiguration.class); @@ -52,12 +50,11 @@ public class ExtProducerResetConfiguration implements ApplicationContextAware, S private RocketMQProperties rocketMQProperties; - private ObjectMapper objectMapper; + private RocketMQMessageConverter rocketMQMessageConverter; - public ExtProducerResetConfiguration(ObjectMapper rocketMQMessageObjectMapper, - StandardEnvironment environment, - RocketMQProperties rocketMQProperties) { - this.objectMapper = rocketMQMessageObjectMapper; + public ExtProducerResetConfiguration(RocketMQMessageConverter rocketMQMessageConverter, + StandardEnvironment environment, RocketMQProperties rocketMQProperties) { + this.rocketMQMessageConverter = rocketMQMessageConverter; this.environment = environment; this.rocketMQProperties = rocketMQProperties; } @@ -69,11 +66,11 @@ public void setApplicationContext(ApplicationContext applicationContext) throws @Override public void afterSingletonsInstantiated() { - Map beans = this.applicationContext.getBeansWithAnnotation(ExtRocketMQTemplateConfiguration.class); + Map beans = this.applicationContext.getBeansWithAnnotation(ExtRocketMQTemplateConfiguration.class) + .entrySet().stream().filter(entry -> !ScopedProxyUtils.isScopedTarget(entry.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - if (Objects.nonNull(beans)) { - beans.forEach(this::registerTemplate); - } + beans.forEach(this::registerTemplate); } private void registerTemplate(String beanName, Object bean) { @@ -94,18 +91,15 @@ private void registerTemplate(String beanName, Object bean) { mqProducer.start(); } catch (MQClientException e) { throw new BeanDefinitionValidationException(String.format("Failed to startup MQProducer for RocketMQTemplate {}", - beanName), e); + beanName), e); } RocketMQTemplate rocketMQTemplate = (RocketMQTemplate) bean; rocketMQTemplate.setProducer(mqProducer); - rocketMQTemplate.setObjectMapper(objectMapper); - - + rocketMQTemplate.setMessageConverter(rocketMQMessageConverter.getMessageConverter()); log.info("Set real producer to :{} {}", beanName, annotation.value()); } private DefaultMQProducer createProducer(ExtRocketMQTemplateConfiguration annotation) { - DefaultMQProducer producer = null; RocketMQProperties.Producer producerConfig = rocketMQProperties.getProducer(); if (producerConfig == null) { @@ -116,23 +110,18 @@ private DefaultMQProducer createProducer(ExtRocketMQTemplateConfiguration annota groupName = StringUtils.isEmpty(groupName) ? producerConfig.getGroup() : groupName; String ak = environment.resolvePlaceholders(annotation.accessKey()); - ak = StringUtils.isEmpty(ak) ? producerConfig.getAccessKey() : annotation.accessKey(); + ak = StringUtils.isEmpty(ak) ? producerConfig.getAccessKey() : ak; String sk = environment.resolvePlaceholders(annotation.secretKey()); - sk = StringUtils.isEmpty(sk) ? producerConfig.getSecretKey() : annotation.secretKey(); + sk = StringUtils.isEmpty(sk) ? producerConfig.getSecretKey() : sk; + boolean isEnableMsgTrace = annotation.enableMsgTrace(); String customizedTraceTopic = environment.resolvePlaceholders(annotation.customizedTraceTopic()); customizedTraceTopic = StringUtils.isEmpty(customizedTraceTopic) ? producerConfig.getCustomizedTraceTopic() : customizedTraceTopic; - if (!StringUtils.isEmpty(ak) && !StringUtils.isEmpty(sk)) { - producer = new DefaultMQProducer(groupName, new AclClientRPCHook(new SessionCredentials(ak, sk)), - annotation.enableMsgTrace(), customizedTraceTopic); - producer.setVipChannelEnabled(false); - } else { - producer = new DefaultMQProducer(groupName, annotation.enableMsgTrace(), customizedTraceTopic); - } + DefaultMQProducer producer = RocketMQUtil.createDefaultMQProducer(groupName, ak, sk, isEnableMsgTrace, customizedTraceTopic); producer.setNamesrvAddr(nameServer); producer.setSendMsgTimeout(annotation.sendMessageTimeout() == -1 ? producerConfig.getSendMessageTimeout() : annotation.sendMessageTimeout()); - producer.setRetryTimesWhenSendFailed(annotation.retryTimesWhenSendAsyncFailed() == -1 ? producerConfig.getRetryTimesWhenSendFailed() : annotation.retryTimesWhenSendAsyncFailed()); + producer.setRetryTimesWhenSendFailed(annotation.retryTimesWhenSendFailed() == -1 ? producerConfig.getRetryTimesWhenSendFailed() : annotation.retryTimesWhenSendFailed()); producer.setRetryTimesWhenSendAsyncFailed(annotation.retryTimesWhenSendAsyncFailed() == -1 ? producerConfig.getRetryTimesWhenSendAsyncFailed() : annotation.retryTimesWhenSendAsyncFailed()); producer.setMaxMessageSize(annotation.maxMessageSize() == -1 ? producerConfig.getMaxMessageSize() : annotation.maxMessageSize()); producer.setCompressMsgBodyOverHowmuch(annotation.compressMessageBodyThreshold() == -1 ? producerConfig.getCompressMessageBodyThreshold() : annotation.compressMessageBodyThreshold()); @@ -141,18 +130,12 @@ private DefaultMQProducer createProducer(ExtRocketMQTemplateConfiguration annota return producer; } - private void validate(ExtRocketMQTemplateConfiguration annotation, GenericApplicationContext genericApplicationContext) { + private void validate(ExtRocketMQTemplateConfiguration annotation, + GenericApplicationContext genericApplicationContext) { if (genericApplicationContext.isBeanNameInUse(annotation.value())) { throw new BeanDefinitionValidationException(String.format("Bean {} has been used in Spring Application Context, " + - "please check the @ExtRocketMQTemplateConfiguration", - annotation.value())); - } - - if (rocketMQProperties.getNameServer() == null || - rocketMQProperties.getNameServer().equals(environment.resolvePlaceholders(annotation.nameServer()))) { - throw new BeanDefinitionValidationException( - "Bad annotation definition in @ExtRocketMQTemplateConfiguration, nameServer property is same with " + - "global property, please use the default RocketMQTemplate!"); + "please check the @ExtRocketMQTemplateConfiguration", + annotation.value())); } } } \ No newline at end of file diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/ListenerContainerConfiguration.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/ListenerContainerConfiguration.java index 2d6cb3fd..1e3f1eda 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/ListenerContainerConfiguration.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/ListenerContainerConfiguration.java @@ -17,16 +17,22 @@ package org.apache.rocketmq.spring.autoconfigure; -import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.spring.annotation.ConsumeMode; import org.apache.rocketmq.spring.annotation.MessageModel; import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.core.RocketMQListener; +import org.apache.rocketmq.spring.core.RocketMQReplyListener; import org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer; +import org.apache.rocketmq.spring.support.RocketMQMessageConverter; 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; @@ -38,11 +44,6 @@ import org.springframework.core.env.StandardEnvironment; import org.springframework.util.StringUtils; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicLong; - - @Configuration public class ListenerContainerConfiguration implements ApplicationContextAware, SmartInitializingSingleton { private final static Logger log = LoggerFactory.getLogger(ListenerContainerConfiguration.class); @@ -55,12 +56,11 @@ public class ListenerContainerConfiguration implements ApplicationContextAware, private RocketMQProperties rocketMQProperties; - private ObjectMapper objectMapper; + private RocketMQMessageConverter rocketMQMessageConverter; - public ListenerContainerConfiguration(ObjectMapper rocketMQMessageObjectMapper, - StandardEnvironment environment, - RocketMQProperties rocketMQProperties) { - this.objectMapper = rocketMQMessageObjectMapper; + public ListenerContainerConfiguration(RocketMQMessageConverter rocketMQMessageConverter, + StandardEnvironment environment, RocketMQProperties rocketMQProperties) { + this.rocketMQMessageConverter = rocketMQMessageConverter; this.environment = environment; this.rocketMQProperties = rocketMQProperties; } @@ -72,21 +72,39 @@ public void setApplicationContext(ApplicationContext applicationContext) throws @Override public void afterSingletonsInstantiated() { - Map beans = this.applicationContext.getBeansWithAnnotation(RocketMQMessageListener.class); + Map beans = this.applicationContext.getBeansWithAnnotation(RocketMQMessageListener.class) + .entrySet().stream().filter(entry -> !ScopedProxyUtils.isScopedTarget(entry.getKey())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - if (Objects.nonNull(beans)) { - beans.forEach(this::registerContainer); - } + beans.forEach(this::registerContainer); } private void registerContainer(String beanName, Object bean) { Class clazz = AopProxyUtils.ultimateTargetClass(bean); - if (!RocketMQListener.class.isAssignableFrom(bean.getClass())) { - throw new IllegalStateException(clazz + " is not instance of " + RocketMQListener.class.getName()); + if (RocketMQListener.class.isAssignableFrom(bean.getClass()) && RocketMQReplyListener.class.isAssignableFrom(bean.getClass())) { + throw new IllegalStateException(clazz + " cannot be both instance of " + RocketMQListener.class.getName() + " and " + RocketMQReplyListener.class.getName()); + } + + if (!RocketMQListener.class.isAssignableFrom(bean.getClass()) && !RocketMQReplyListener.class.isAssignableFrom(bean.getClass())) { + throw new IllegalStateException(clazz + " is not instance of " + RocketMQListener.class.getName() + " or " + RocketMQReplyListener.class.getName()); } RocketMQMessageListener annotation = clazz.getAnnotation(RocketMQMessageListener.class); + + String consumerGroup = this.environment.resolvePlaceholders(annotation.consumerGroup()); + String topic = this.environment.resolvePlaceholders(annotation.topic()); + + boolean listenerEnabled = + (boolean) rocketMQProperties.getConsumer().getListeners().getOrDefault(consumerGroup, Collections.EMPTY_MAP) + .getOrDefault(topic, true); + + if (!listenerEnabled) { + log.debug( + "Consumer Listener (group:{},topic:{}) is not enabled by configuration, will ignore initialization.", + consumerGroup, topic); + return; + } validate(annotation); String containerBeanName = String.format("%s_%s", DefaultRocketMQListenerContainer.class.getName(), @@ -109,9 +127,12 @@ private void registerContainer(String beanName, Object bean) { log.info("Register the listener to container, listenerBeanName:{}, containerBeanName:{}", beanName, containerBeanName); } - private DefaultRocketMQListenerContainer createRocketMQListenerContainer(String name, Object bean, RocketMQMessageListener annotation) { + private DefaultRocketMQListenerContainer createRocketMQListenerContainer(String name, Object bean, + RocketMQMessageListener annotation) { DefaultRocketMQListenerContainer container = new DefaultRocketMQListenerContainer(); + container.setRocketMQMessageListener(annotation); + String nameServer = environment.resolvePlaceholders(annotation.nameServer()); nameServer = StringUtils.isEmpty(nameServer) ? rocketMQProperties.getNameServer() : nameServer; String accessChannel = environment.resolvePlaceholders(annotation.accessChannel()); @@ -120,11 +141,18 @@ private DefaultRocketMQListenerContainer createRocketMQListenerContainer(String container.setAccessChannel(AccessChannel.valueOf(accessChannel)); } container.setTopic(environment.resolvePlaceholders(annotation.topic())); + String tags = environment.resolvePlaceholders(annotation.selectorExpression()); + if (!StringUtils.isEmpty(tags)) { + container.setSelectorExpression(tags); + } container.setConsumerGroup(environment.resolvePlaceholders(annotation.consumerGroup())); - container.setRocketMQMessageListener(annotation); - container.setRocketMQListener((RocketMQListener) bean); - container.setObjectMapper(objectMapper); - container.setName(name); // REVIEW ME, use the same clientId or multiple? + if (RocketMQListener.class.isAssignableFrom(bean.getClass())) { + container.setRocketMQListener((RocketMQListener) bean); + } else if (RocketMQReplyListener.class.isAssignableFrom(bean.getClass())) { + container.setRocketMQReplyListener((RocketMQReplyListener) bean); + } + container.setMessageConverter(rocketMQMessageConverter.getMessageConverter()); + container.setName(name); return container; } diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/JacksonFallbackConfiguration.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/MessageConverterConfiguration.java similarity index 77% rename from rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/JacksonFallbackConfiguration.java rename to rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/MessageConverterConfiguration.java index d25ca8d8..5f7e4194 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/JacksonFallbackConfiguration.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/MessageConverterConfiguration.java @@ -17,18 +17,21 @@ package org.apache.rocketmq.spring.autoconfigure; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.rocketmq.spring.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(ObjectMapper.class) -class JacksonFallbackConfiguration { +@ConditionalOnMissingBean(RocketMQMessageConverter.class) +class MessageConverterConfiguration { @Bean - public ObjectMapper rocketMQMessageObjectMapper() { - return new ObjectMapper(); + public RocketMQMessageConverter createRocketMQMessageConverter() { + return new RocketMQMessageConverter(); } } 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 51963698..b9ec643e 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 @@ -17,32 +17,34 @@ package org.apache.rocketmq.spring.autoconfigure; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.rocketmq.acl.common.AclClientRPCHook; -import org.apache.rocketmq.acl.common.SessionCredentials; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.client.MQAdmin; +import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; +import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; -import org.apache.rocketmq.spring.config.RocketMQConfigUtils; -import org.apache.rocketmq.spring.config.RocketMQTransactionAnnotationProcessor; -import org.apache.rocketmq.spring.config.TransactionHandlerRegistry; +import org.apache.rocketmq.spring.annotation.MessageModel; +import org.apache.rocketmq.spring.annotation.SelectorType; import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.apache.rocketmq.spring.support.RocketMQMessageConverter; +import org.apache.rocketmq.spring.support.RocketMQUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.config.BeanDefinition; 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.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; 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.context.annotation.Role; import org.springframework.core.env.Environment; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -51,16 +53,30 @@ @Configuration @EnableConfigurationProperties(RocketMQProperties.class) -@ConditionalOnClass({ MQAdmin.class, ObjectMapper.class }) +@ConditionalOnClass({MQAdmin.class}) @ConditionalOnProperty(prefix = "rocketmq", value = "name-server", matchIfMissing = true) -@Import({ JacksonFallbackConfiguration.class, ListenerContainerConfiguration.class, ExtProducerResetConfiguration.class }) -@AutoConfigureAfter(JacksonAutoConfiguration.class) -public class RocketMQAutoConfiguration { +@Import({MessageConverterConfiguration.class, ListenerContainerConfiguration.class, ExtProducerResetConfiguration.class, ExtConsumerResetConfiguration.class, RocketMQTransactionConfiguration.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 = + "rocketMQTemplate"; + public static final String PRODUCER_BEAN_NAME = "defaultMQProducer"; + public static final String CONSUMER_BEAN_NAME = "defaultLitePullConsumer"; + @Autowired private Environment environment; + private ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + @PostConstruct public void checkProperties() { String nameServer = environment.getProperty("rocketmq.name-server", String.class); @@ -70,8 +86,7 @@ public void checkProperties() { } } - - @Bean + @Bean(PRODUCER_BEAN_NAME) @ConditionalOnMissingBean(DefaultMQProducer.class) @ConditionalOnProperty(prefix = "rocketmq", value = {"name-server", "producer.group"}) public DefaultMQProducer defaultMQProducer(RocketMQProperties rocketMQProperties) { @@ -83,18 +98,12 @@ public DefaultMQProducer defaultMQProducer(RocketMQProperties rocketMQProperties String accessChannel = rocketMQProperties.getAccessChannel(); - DefaultMQProducer producer; String ak = rocketMQProperties.getProducer().getAccessKey(); String sk = rocketMQProperties.getProducer().getSecretKey(); - if (!StringUtils.isEmpty(ak) && !StringUtils.isEmpty(sk)) { - producer = new DefaultMQProducer(groupName, new AclClientRPCHook(new SessionCredentials(ak, sk)), - rocketMQProperties.getProducer().isEnableMsgTrace(), - rocketMQProperties.getProducer().getCustomizedTraceTopic()); - producer.setVipChannelEnabled(false); - } else { - producer = new DefaultMQProducer(groupName, rocketMQProperties.getProducer().isEnableMsgTrace(), - rocketMQProperties.getProducer().getCustomizedTraceTopic()); - } + boolean isEnableMsgTrace = rocketMQProperties.getProducer().isEnableMsgTrace(); + String customizedTraceTopic = rocketMQProperties.getProducer().getCustomizedTraceTopic(); + + DefaultMQProducer producer = RocketMQUtil.createDefaultMQProducer(groupName, ak, sk, isEnableMsgTrace, customizedTraceTopic); producer.setNamesrvAddr(nameServer); if (!StringUtils.isEmpty(accessChannel)) { @@ -110,30 +119,59 @@ public DefaultMQProducer defaultMQProducer(RocketMQProperties rocketMQProperties return producer; } + @Bean(CONSUMER_BEAN_NAME) + @ConditionalOnMissingBean(DefaultLitePullConsumer.class) + @ConditionalOnProperty(prefix = "rocketmq", value = {"name-server", "consumer.group", "consumer.topic"}) + public DefaultLitePullConsumer defaultLitePullConsumer(RocketMQProperties rocketMQProperties) + throws MQClientException { + RocketMQProperties.Consumer consumerConfig = rocketMQProperties.getConsumer(); + String nameServer = rocketMQProperties.getNameServer(); + String groupName = consumerConfig.getGroup(); + String topicName = consumerConfig.getTopic(); + Assert.hasText(nameServer, "[rocketmq.name-server] must not be null"); + Assert.hasText(groupName, "[rocketmq.consumer.group] must not be null"); + Assert.hasText(topicName, "[rocketmq.consumer.topic] must not be null"); + + String accessChannel = rocketMQProperties.getAccessChannel(); + MessageModel messageModel = MessageModel.valueOf(consumerConfig.getMessageModel()); + SelectorType selectorType = SelectorType.valueOf(consumerConfig.getSelectorType()); + String selectorExpression = consumerConfig.getSelectorExpression(); + String ak = consumerConfig.getAccessKey(); + String sk = consumerConfig.getSecretKey(); + int pullBatchSize = consumerConfig.getPullBatchSize(); + + DefaultLitePullConsumer litePullConsumer = RocketMQUtil.createDefaultLitePullConsumer(nameServer, accessChannel, + groupName, topicName, messageModel, selectorType, selectorExpression, ak, sk, pullBatchSize); + return litePullConsumer; + } + @Bean(destroyMethod = "destroy") - @ConditionalOnBean(DefaultMQProducer.class) - @ConditionalOnMissingBean(name = RocketMQConfigUtils.ROCKETMQ_TEMPLATE_DEFAULT_GLOBAL_NAME) - public RocketMQTemplate rocketMQTemplate(DefaultMQProducer mqProducer, ObjectMapper rocketMQMessageObjectMapper) { + @Conditional(ProducerOrConsumerPropertyCondition.class) + @ConditionalOnMissingBean(name = ROCKETMQ_TEMPLATE_DEFAULT_GLOBAL_NAME) + public RocketMQTemplate rocketMQTemplate(RocketMQMessageConverter rocketMQMessageConverter) { RocketMQTemplate rocketMQTemplate = new RocketMQTemplate(); - rocketMQTemplate.setProducer(mqProducer); - rocketMQTemplate.setObjectMapper(rocketMQMessageObjectMapper); + if (applicationContext.containsBean(PRODUCER_BEAN_NAME)) { + rocketMQTemplate.setProducer((DefaultMQProducer) applicationContext.getBean(PRODUCER_BEAN_NAME)); + } + if (applicationContext.containsBean(CONSUMER_BEAN_NAME)) { + rocketMQTemplate.setConsumer((DefaultLitePullConsumer) applicationContext.getBean(CONSUMER_BEAN_NAME)); + } + rocketMQTemplate.setMessageConverter(rocketMQMessageConverter.getMessageConverter()); return rocketMQTemplate; } - @Bean - @ConditionalOnBean(name = RocketMQConfigUtils.ROCKETMQ_TEMPLATE_DEFAULT_GLOBAL_NAME) - @ConditionalOnMissingBean(TransactionHandlerRegistry.class) - public TransactionHandlerRegistry transactionHandlerRegistry(@Qualifier(RocketMQConfigUtils.ROCKETMQ_TEMPLATE_DEFAULT_GLOBAL_NAME) - RocketMQTemplate template) { - return new TransactionHandlerRegistry(template); - } + static class ProducerOrConsumerPropertyCondition extends AnyNestedCondition { - @Bean(name = RocketMQConfigUtils.ROCKETMQ_TRANSACTION_ANNOTATION_PROCESSOR_BEAN_NAME) - @ConditionalOnBean(TransactionHandlerRegistry.class) - @Role(BeanDefinition.ROLE_INFRASTRUCTURE) - public static RocketMQTransactionAnnotationProcessor transactionAnnotationProcessor( - TransactionHandlerRegistry transactionHandlerRegistry) { - return new RocketMQTransactionAnnotationProcessor(transactionHandlerRegistry); - } + public ProducerOrConsumerPropertyCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnBean(DefaultMQProducer.class) + static class DefaultMQProducerExistsCondition { + } + @ConditionalOnBean(DefaultLitePullConsumer.class) + static class DefaultLitePullConsumerExistsCondition { + } + } } diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/RocketMQProperties.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/RocketMQProperties.java index 62ca2be6..98a88f73 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/RocketMQProperties.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/RocketMQProperties.java @@ -17,9 +17,12 @@ package org.apache.rocketmq.spring.autoconfigure; -import org.apache.rocketmq.common.MixAll; +import org.apache.rocketmq.common.topic.TopicValidator; import org.springframework.boot.context.properties.ConfigurationProperties; +import java.util.HashMap; +import java.util.Map; + @SuppressWarnings("WeakerAccess") @ConfigurationProperties(prefix = "rocketmq") public class RocketMQProperties { @@ -30,12 +33,22 @@ public class RocketMQProperties { private String nameServer; /** - * Enum type for accesChannel, values: LOCAL, CLOUD + * Enum type for accessChannel, values: LOCAL, CLOUD */ private String accessChannel; private Producer producer; + /** + * Configure enable listener or not. + * In some particular cases, if you don't want the the listener is enabled when container startup, + * the configuration pattern is like this : + * rocketmq.consumer.listeners...enabled= + *

+ * the listener is enabled by default. + */ + private Consumer consumer = new Consumer(); + public String getNameServer() { return nameServer; } @@ -63,7 +76,7 @@ public void setProducer(RocketMQProperties.Producer producer) { public static class Producer { /** - * Name of producer. + * Group name of producer. */ private String group; @@ -117,7 +130,7 @@ public static class Producer { /** * The name value of message trace topic.If you don't config,you can use the default trace topic name. */ - private String customizedTraceTopic = MixAll.RMQ_SYS_TRACE_TOPIC; + private String customizedTraceTopic = TopicValidator.RMQ_SYS_TRACE_TOPIC; public String getGroup() { return group; @@ -207,4 +220,137 @@ public void setCustomizedTraceTopic(String customizedTraceTopic) { this.customizedTraceTopic = customizedTraceTopic; } } + + public Consumer getConsumer() { + return consumer; + } + + public void setConsumer(Consumer consumer) { + this.consumer = consumer; + } + + public static final class Consumer { + /** + * Group name of consumer. + */ + private String group; + + /** + * Topic name of consumer. + */ + private String topic; + + /** + * Control message mode, if you want all subscribers receive message all message, broadcasting is a good choice. + */ + private String messageModel = "CLUSTERING"; + + /** + * Control how to selector message. + * + */ + private String selectorType = "TAG"; + + /** + * Control which message can be select. + */ + private String selectorExpression = "*"; + + /** + * The property of "access-key". + */ + private String accessKey; + + /** + * The property of "secret-key". + */ + private String secretKey; + + /** + * Maximum number of messages pulled each time. + */ + private int pullBatchSize = 10; + + /** + * listener configuration container + * the pattern is like this: + * group1.topic1 = false + * group2.topic2 = true + * group3.topic3 = false + */ + private Map> listeners = new HashMap<>(); + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getMessageModel() { + return messageModel; + } + + public void setMessageModel(String messageModel) { + this.messageModel = messageModel; + } + + public String getSelectorType() { + return selectorType; + } + + public void setSelectorType(String selectorType) { + this.selectorType = selectorType; + } + + public String getSelectorExpression() { + return selectorExpression; + } + + public void setSelectorExpression(String selectorExpression) { + this.selectorExpression = selectorExpression; + } + + 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 int getPullBatchSize() { + return pullBatchSize; + } + + public void setPullBatchSize(int pullBatchSize) { + this.pullBatchSize = pullBatchSize; + } + + public Map> getListeners() { + return listeners; + } + + public void setListeners(Map> listeners) { + this.listeners = listeners; + } + } + } diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/RocketMQTransactionConfiguration.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/RocketMQTransactionConfiguration.java new file mode 100644 index 00000000..74badbb6 --- /dev/null +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/autoconfigure/RocketMQTransactionConfiguration.java @@ -0,0 +1,76 @@ +/* + * 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.spring.autoconfigure; + +import java.util.Map; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import org.apache.rocketmq.client.producer.TransactionMQProducer; +import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener; +import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.apache.rocketmq.spring.support.RocketMQUtil; +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; + +@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::registerTransactionListener); + } + + private void registerTransactionListener(String beanName, Object bean) { + Class clazz = AopProxyUtils.ultimateTargetClass(bean); + + if (!RocketMQLocalTransactionListener.class.isAssignableFrom(bean.getClass())) { + throw new IllegalStateException(clazz + " is not instance of " + RocketMQLocalTransactionListener.class.getName()); + } + RocketMQTransactionListener annotation = clazz.getAnnotation(RocketMQTransactionListener.class); + RocketMQTemplate rocketMQTemplate = (RocketMQTemplate) applicationContext.getBean(annotation.rocketMQTemplateBeanName()); + if (((TransactionMQProducer) rocketMQTemplate.getProducer()).getTransactionListener() != null) { + throw new IllegalStateException(annotation.rocketMQTemplateBeanName() + " already exists RocketMQLocalTransactionListener"); + } + ((TransactionMQProducer) rocketMQTemplate.getProducer()).setExecutorService(new ThreadPoolExecutor(annotation.corePoolSize(), annotation.maximumPoolSize(), + annotation.keepAliveTime(), TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(annotation.blockingQueueSize()))); + ((TransactionMQProducer) rocketMQTemplate.getProducer()).setTransactionListener(RocketMQUtil.convert((RocketMQLocalTransactionListener) bean)); + log.debug("RocketMQLocalTransactionListener {} register to {} success", clazz.getName(), annotation.rocketMQTemplateBeanName()); + } +} diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/RocketMQTransactionAnnotationProcessor.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/RocketMQTransactionAnnotationProcessor.java deleted file mode 100644 index a8022650..00000000 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/RocketMQTransactionAnnotationProcessor.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.spring.config; - -import org.apache.rocketmq.client.exception.MQClientException; -import org.apache.rocketmq.remoting.RPCHook; -import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener; -import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener; -import org.apache.rocketmq.spring.support.RocketMQUtil; -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationUtils; - -import java.util.Collections; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class RocketMQTransactionAnnotationProcessor - implements BeanPostProcessor, Ordered, ApplicationContextAware { - private final static Logger log = LoggerFactory.getLogger(RocketMQTransactionAnnotationProcessor.class); - - private ApplicationContext applicationContext; - private final Set> nonProcessedClasses = - Collections.newSetFromMap(new ConcurrentHashMap, Boolean>(64)); - - private TransactionHandlerRegistry transactionHandlerRegistry; - - public RocketMQTransactionAnnotationProcessor(TransactionHandlerRegistry transactionHandlerRegistry) { - this.transactionHandlerRegistry = transactionHandlerRegistry; - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.applicationContext = applicationContext; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - return bean; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (!this.nonProcessedClasses.contains(bean.getClass())) { - Class targetClass = AopUtils.getTargetClass(bean); - RocketMQTransactionListener listener = AnnotationUtils.findAnnotation(targetClass, RocketMQTransactionListener.class); - this.nonProcessedClasses.add(bean.getClass()); - if (listener == null) { // for quick search - log.trace("No @RocketMQTransactionListener annotations found on bean type: {}", bean.getClass()); - } else { - try { - processTransactionListenerAnnotation(listener, bean); - } catch (MQClientException e) { - log.error("Failed to process annotation " + listener, e); - throw new BeanCreationException("Failed to process annotation " + listener, e); - } - } - } - - return bean; - } - - private void processTransactionListenerAnnotation(RocketMQTransactionListener listener, Object bean) - throws MQClientException { - if (transactionHandlerRegistry == null) { - throw new MQClientException("Bad usage of @RocketMQTransactionListener, " + - "the class must work with RocketMQTemplate", null); - } - if (!RocketMQLocalTransactionListener.class.isAssignableFrom(bean.getClass())) { - throw new MQClientException("Bad usage of @RocketMQTransactionListener, " + - "the class must implement interface RocketMQLocalTransactionListener", - null); - } - TransactionHandler transactionHandler = new TransactionHandler(); - transactionHandler.setBeanFactory(this.applicationContext.getAutowireCapableBeanFactory()); - transactionHandler.setName(listener.txProducerGroup()); - transactionHandler.setBeanName(bean.getClass().getName()); - transactionHandler.setListener((RocketMQLocalTransactionListener) bean); - transactionHandler.setCheckExecutor(listener.corePoolSize(), listener.maximumPoolSize(), - listener.keepAliveTime(), listener.blockingQueueSize()); - - RPCHook rpcHook = RocketMQUtil.getRPCHookByAkSk(applicationContext.getEnvironment(), - listener.accessKey(), listener.secretKey()); - - if (Objects.nonNull(rpcHook)) { - transactionHandler.setRpcHook(rpcHook); - } else { - log.debug("Access-key or secret-key not configure in " + listener + "."); - } - - transactionHandlerRegistry.registerTransactionHandler(transactionHandler); - } - - @Override - public int getOrder() { - return LOWEST_PRECEDENCE; - } - -} diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/TransactionHandler.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/TransactionHandler.java deleted file mode 100644 index f6ce61c3..00000000 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/TransactionHandler.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.spring.config; - -import org.apache.rocketmq.remoting.RPCHook; -import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener; -import org.springframework.beans.factory.BeanFactory; - -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -class TransactionHandler { - private String name; - private String beanName; - private RocketMQLocalTransactionListener bean; - private BeanFactory beanFactory; - private ThreadPoolExecutor checkExecutor; - private RPCHook rpcHook; - - public String getBeanName() { - return beanName; - } - - public void setBeanName(String beanName) { - this.beanName = beanName; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public RPCHook getRpcHook() { - return rpcHook; - } - - public void setRpcHook(RPCHook rpcHook) { - this.rpcHook = rpcHook; - } - - public BeanFactory getBeanFactory() { - return beanFactory; - } - - public void setBeanFactory(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - public void setListener(RocketMQLocalTransactionListener listener) { - this.bean = listener; - } - - public RocketMQLocalTransactionListener getListener() { - return this.bean; - } - - public void setCheckExecutor(int corePoolSize, int maxPoolSize, long keepAliveTime, int blockingQueueSize) { - this.checkExecutor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, - keepAliveTime, TimeUnit.MILLISECONDS, - new LinkedBlockingDeque<>(blockingQueueSize)); - } - - public ThreadPoolExecutor getCheckExecutor() { - return checkExecutor; - } -} diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/TransactionHandlerRegistry.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/TransactionHandlerRegistry.java deleted file mode 100644 index 7307a318..00000000 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/TransactionHandlerRegistry.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.spring.config; - -import io.netty.util.internal.ConcurrentSet; -import org.apache.rocketmq.client.exception.MQClientException; -import org.apache.rocketmq.spring.core.RocketMQTemplate; -import org.springframework.beans.factory.DisposableBean; - -import java.util.Set; - -public class TransactionHandlerRegistry implements DisposableBean { - private RocketMQTemplate rocketMQTemplate; - - private final Set listenerContainers = new ConcurrentSet<>(); - - public TransactionHandlerRegistry(RocketMQTemplate template) { - this.rocketMQTemplate = template; - } - - @Override - public void destroy() throws Exception { - listenerContainers.clear(); - } - - public void registerTransactionHandler(TransactionHandler handler) throws MQClientException { - if (listenerContainers.contains(handler.getName())) { - throw new MQClientException(-1, - String - .format("The transaction name [%s] has been defined in TransactionListener [%s]", handler.getName(), - handler.getBeanName())); - } - listenerContainers.add(handler.getName()); - - rocketMQTemplate.createAndStartTransactionMQProducer(handler.getName(), handler.getListener(), handler.getCheckExecutor(), handler.getRpcHook()); - } -} diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/core/RocketMQLocalRequestCallback.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/core/RocketMQLocalRequestCallback.java new file mode 100644 index 00000000..56b15ff9 --- /dev/null +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/core/RocketMQLocalRequestCallback.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.spring.core; + +/** + * Classes implementing this interface are used for processing callback events after receiving + * reply messages from consumers. + * + * @param the type of message that wanted to receive from consumer + */ +public interface RocketMQLocalRequestCallback { + void onSuccess(final T message); + + void onException(final Throwable e); +} diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/RocketMQConfigUtils.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/core/RocketMQReplyListener.java similarity index 58% rename from rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/RocketMQConfigUtils.java rename to rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/core/RocketMQReplyListener.java index 3e1c573d..916368d3 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/config/RocketMQConfigUtils.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/core/RocketMQReplyListener.java @@ -15,18 +15,18 @@ * limitations under the License. */ -package org.apache.rocketmq.spring.config; +package org.apache.rocketmq.spring.core; -public class RocketMQConfigUtils { +/** + * The consumer supporting request-reply should implement this interface. + * + * @param the type of data received by the listener + * @param the type of data replying to producer + */ +public interface RocketMQReplyListener { /** - * The bean name of the internally managed RocketMQ transaction annotation processor. + * @param message data received by the listener + * @return data replying to producer */ - public static final String ROCKETMQ_TRANSACTION_ANNOTATION_PROCESSOR_BEAN_NAME = - "org.springframework.rocketmq.spring.starter.internalRocketMQTransAnnotationProcessor"; - - public static final String ROCKETMQ_TRANSACTION_DEFAULT_GLOBAL_NAME = - "rocketmq_transaction_default_global_name"; - - public static final String ROCKETMQ_TEMPLATE_DEFAULT_GLOBAL_NAME = - "rocketMQTemplate"; + R onMessage(T message); } diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/core/RocketMQTemplate.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/core/RocketMQTemplate.java index 6b5d06c0..a18e7815 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/core/RocketMQTemplate.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/core/RocketMQTemplate.java @@ -17,52 +17,56 @@ package org.apache.rocketmq.spring.core; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; +import org.apache.rocketmq.client.producer.RequestCallback; import org.apache.rocketmq.client.producer.SendCallback; import org.apache.rocketmq.client.producer.SendResult; import org.apache.rocketmq.client.producer.TransactionMQProducer; import org.apache.rocketmq.client.producer.TransactionSendResult; import org.apache.rocketmq.client.producer.selector.SelectMessageQueueByHash; -import org.apache.rocketmq.remoting.RPCHook; -import org.apache.rocketmq.spring.config.RocketMQConfigUtils; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.spring.support.RocketMQMessageConverter; import org.apache.rocketmq.spring.support.RocketMQUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.framework.AopProxyUtils; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.messaging.Message; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.MessagingException; +import org.springframework.messaging.converter.SmartMessageConverter; import org.springframework.messaging.core.AbstractMessageSendingTemplate; import org.springframework.messaging.core.MessagePostProcessor; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.util.Assert; import org.springframework.util.MimeTypeUtils; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @SuppressWarnings({"WeakerAccess", "unused"}) public class RocketMQTemplate extends AbstractMessageSendingTemplate implements InitializingBean, DisposableBean { - private static final Logger log = LoggerFactory.getLogger(RocketMQTemplate.class); + private static final Logger log = LoggerFactory.getLogger(RocketMQTemplate.class); private DefaultMQProducer producer; - private ObjectMapper objectMapper; + private DefaultLitePullConsumer consumer; private String charset = "UTF-8"; private MessageQueueSelector messageQueueSelector = new SelectMessageQueueByHash(); - private final Map cache = new ConcurrentHashMap<>(); //only put TransactionMQProducer by now!!! + private RocketMQMessageConverter rocketMQMessageConverter = new RocketMQMessageConverter(); public DefaultMQProducer getProducer() { return producer; @@ -72,12 +76,12 @@ public void setProducer(DefaultMQProducer producer) { this.producer = producer; } - public ObjectMapper getObjectMapper() { - return objectMapper; + public DefaultLitePullConsumer getConsumer() { + return consumer; } - public void setObjectMapper(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; + public void setConsumer(DefaultLitePullConsumer consumer) { + this.consumer = consumer; } public String getCharset() { @@ -96,6 +100,360 @@ public void setMessageQueueSelector(MessageQueueSelector messageQueueSelector) { this.messageQueueSelector = messageQueueSelector; } + public void setAsyncSenderExecutor(ExecutorService asyncSenderExecutor) { + this.producer.setAsyncSenderExecutor(asyncSenderExecutor); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param type The type of T + * @return + */ + public T sendAndReceive(String destination, Message message, Type type) { + return sendAndReceive(destination, message, type, null, producer.getSendMsgTimeout(), 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param type The type of T + * @return + */ + public T sendAndReceive(String destination, Object payload, Type type) { + return sendAndReceive(destination, payload, type, null, producer.getSendMsgTimeout(), 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param type The type of T + * @param timeout send timeout in millis + * @return + */ + public T sendAndReceive(String destination, Message message, Type type, long timeout) { + return sendAndReceive(destination, message, type, null, timeout, 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param type The type of T + * @param timeout send timeout in millis + * @return + */ + public T sendAndReceive(String destination, Object payload, Type type, long timeout) { + return sendAndReceive(destination, payload, type, null, timeout, 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param type The type of T + * @param timeout send timeout in millis + * @param delayLevel message delay level(0 means no delay) + * @return + */ + public T sendAndReceive(String destination, Message message, Type type, long timeout, int delayLevel) { + return sendAndReceive(destination, message, type, null, timeout, delayLevel); + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param type The type of T + * @param timeout send timeout in millis + * @param delayLevel message delay level(0 means no delay) + * @return + */ + public T sendAndReceive(String destination, Object payload, Type type, long timeout, int delayLevel) { + return sendAndReceive(destination, payload, type, null, timeout, delayLevel); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param type The type of T + * @param hashKey needed when sending message orderly + * @return + */ + public T sendAndReceive(String destination, Message message, Type type, String hashKey) { + return sendAndReceive(destination, message, type, hashKey, producer.getSendMsgTimeout(), 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param type The type of T + * @param hashKey needed when sending message orderly + * @return + */ + public T sendAndReceive(String destination, Object payload, Type type, String hashKey) { + return sendAndReceive(destination, payload, type, hashKey, producer.getSendMsgTimeout(), 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param type The type of T + * @param hashKey needed when sending message orderly + * @param timeout send timeout in millis + * @return + */ + public T sendAndReceive(String destination, Message message, Type type, String hashKey, long timeout) { + return sendAndReceive(destination, message, type, hashKey, timeout, 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param type The type of T + * @param hashKey + * @return + */ + public T sendAndReceive(String destination, Object payload, Type type, String hashKey, long timeout) { + return sendAndReceive(destination, payload, type, hashKey, timeout, 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param type The type that receive + * @param hashKey needed when sending message orderly + * @param timeout send timeout in millis + * @param delayLevel message delay level(0 means no delay) + * @return + */ + public T sendAndReceive(String destination, Message message, Type type, String hashKey, + long timeout, int delayLevel) { + 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"); + } + + try { + org.apache.rocketmq.common.message.Message rocketMsg = this.createRocketMqMessage(destination, message); + if (delayLevel > 0) { + rocketMsg.setDelayTimeLevel(delayLevel); + } + MessageExt replyMessage; + + if (Objects.isNull(hashKey) || hashKey.isEmpty()) { + replyMessage = (MessageExt) producer.request(rocketMsg, timeout); + } else { + replyMessage = (MessageExt) producer.request(rocketMsg, messageQueueSelector, hashKey, timeout); + } + return replyMessage != null ? (T) doConvertMessage(replyMessage, type) : null; + } catch (Exception e) { + log.error("send request message failed. destination:{}, message:{} ", destination, message); + throw new MessagingException(e.getMessage(), e); + } + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param type The type that receive + * @param hashKey needed when sending message orderly + * @param timeout send timeout in millis + * @param delayLevel message delay level(0 means no delay) + * @return + */ + public T sendAndReceive(String destination, Object payload, Type type, String hashKey, + long timeout, int delayLevel) { + Message message = MessageBuilder.withPayload(payload).build(); + return sendAndReceive(destination, message, type, hashKey, timeout, delayLevel); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @return + */ + public void sendAndReceive(String destination, Message message, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback) { + sendAndReceive(destination, message, rocketMQLocalRequestCallback, null, producer.getSendMsgTimeout(), 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @return + */ + public void sendAndReceive(String destination, Object payload, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback) { + sendAndReceive(destination, payload, rocketMQLocalRequestCallback, null, producer.getSendMsgTimeout(), 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @param timeout send timeout in millis + * @return + */ + public void sendAndReceive(String destination, Message message, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback, long timeout) { + sendAndReceive(destination, message, rocketMQLocalRequestCallback, null, timeout, 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @param timeout send timeout in millis + * @return + */ + public void sendAndReceive(String destination, Object payload, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback, long timeout) { + sendAndReceive(destination, payload, rocketMQLocalRequestCallback, null, timeout, 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @param timeout send timeout in millis + * @param delayLevel message delay level(0 means no delay) + * @return + */ + public void sendAndReceive(String destination, Message message, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback, long timeout, int delayLevel) { + sendAndReceive(destination, message, rocketMQLocalRequestCallback, null, timeout, delayLevel); + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @param hashKey needed when sending message orderly + * @return + */ + public void sendAndReceive(String destination, Object payload, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback, String hashKey) { + sendAndReceive(destination, payload, rocketMQLocalRequestCallback, hashKey, producer.getSendMsgTimeout(), 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @param hashKey needed when sending message orderly + * @param timeout send timeout in millis + * @return + */ + public void sendAndReceive(String destination, Message message, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback, String hashKey, long timeout) { + sendAndReceive(destination, message, rocketMQLocalRequestCallback, hashKey, timeout, 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @param hashKey needed when sending message orderly + * @param timeout send timeout in millis + * @return + */ + public void sendAndReceive(String destination, Object payload, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback, String hashKey, long timeout) { + sendAndReceive(destination, payload, rocketMQLocalRequestCallback, hashKey, timeout, 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @param hashKey needed when sending message orderly + * @return + */ + public void sendAndReceive(String destination, Message message, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback, String hashKey) { + sendAndReceive(destination, message, rocketMQLocalRequestCallback, hashKey, producer.getSendMsgTimeout(), 0); + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @param timeout send timeout in millis + * @param delayLevel message delay level(0 means no delay) + * @return + */ + public void sendAndReceive(String destination, Object payload, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback, long timeout, int delayLevel) { + sendAndReceive(destination, payload, rocketMQLocalRequestCallback, null, timeout, delayLevel); + } + + /** + * @param destination formats: `topicName:tags` + * @param payload the payload to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @param hashKey needed when sending message orderly + * @param timeout send timeout in millis + * @param delayLevel message delay level(0 means no delay) + * @return + */ + public void sendAndReceive(String destination, Object payload, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback, String hashKey, long timeout, int delayLevel) { + Message message = MessageBuilder.withPayload(payload).build(); + sendAndReceive(destination, message, rocketMQLocalRequestCallback, hashKey, timeout, delayLevel); + } + + /** + * Send request message in asynchronous mode.

This method returns immediately. On receiving reply message, + * rocketMQLocalRequestCallback will be executed.

+ * + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} the message to be sent. + * @param rocketMQLocalRequestCallback callback that will invoked when reply message received. + * @param hashKey needed when sending message orderly + * @param timeout send timeout in millis + * @param delayLevel message delay level(0 means no delay) + * @return + */ + public void sendAndReceive(String destination, Message message, + RocketMQLocalRequestCallback rocketMQLocalRequestCallback, String hashKey, long timeout, int delayLevel) { + 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"); + } + + try { + org.apache.rocketmq.common.message.Message rocketMsg = this.createRocketMqMessage(destination, message); + if (delayLevel > 0) { + rocketMsg.setDelayTimeLevel(delayLevel); + } + if (timeout <= 0) { + timeout = producer.getSendMsgTimeout(); + } + RequestCallback requestCallback = null; + if (rocketMQLocalRequestCallback != null) { + requestCallback = new RequestCallback() { + @Override public void onSuccess(org.apache.rocketmq.common.message.Message message) { + rocketMQLocalRequestCallback.onSuccess(doConvertMessage((MessageExt) message, getMessageType(rocketMQLocalRequestCallback))); + } + + @Override public void onException(Throwable e) { + rocketMQLocalRequestCallback.onException(e); + } + }; + } + if (Objects.isNull(hashKey) || hashKey.isEmpty()) { + producer.request(rocketMsg, requestCallback, timeout); + } else { + producer.request(rocketMsg, messageQueueSelector, hashKey, requestCallback, timeout); + } + } catch ( + Exception e) { + log.error("send request message failed. destination:{}, message:{} ", destination, message); + throw new MessagingException(e.getMessage(), e); + } + + } + /** *

Send message in synchronous mode. This method returns only when the sending procedure totally completes. * Reliable synchronous transmission is used in extensive scenes, such as important notification messages, SMS @@ -107,7 +465,7 @@ public void setMessageQueueSelector(MessageQueueSelector messageQueueSelector) { * duplication issue. * * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} + * @param message {@link org.springframework.messaging.Message} * @return {@link SendResult} */ public SendResult syncSend(String destination, Message message) { @@ -118,23 +476,34 @@ public SendResult syncSend(String destination, Message message) { * Same to {@link #syncSend(String, Message)} with send timeout specified in addition. * * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} - * @param timeout send timeout with millis + * @param message {@link org.springframework.messaging.Message} + * @param timeout send timeout with millis * @return {@link SendResult} */ public SendResult syncSend(String destination, Message message, long timeout) { return syncSend(destination, message, timeout, 0); } + /** + * syncSend batch messages + * + * @param destination formats: `topicName:tags` + * @param messages Collection of {@link org.springframework.messaging.Message} + * @return {@link SendResult} + */ + public SendResult syncSend(String destination, Collection messages) { + return syncSend(destination, messages, producer.getSendMsgTimeout()); + } + /** * syncSend batch messages in a given timeout. * * @param destination formats: `topicName:tags` - * @param messages Collection of {@link org.springframework.messaging.Message} - * @param timeout send timeout with millis + * @param messages Collection of {@link org.springframework.messaging.Message} + * @param timeout send timeout with millis * @return {@link SendResult} */ - public SendResult syncSend(String destination, Collection> messages, long timeout) { + public SendResult syncSend(String destination, Collection messages, long timeout) { if (Objects.isNull(messages) || messages.size() == 0) { log.error("syncSend with batch failed. destination:{}, messages is empty ", destination); throw new IllegalArgumentException("`messages` can not be empty"); @@ -143,19 +512,19 @@ public SendResult syncSend(String destination, Collection> messages, try { long now = System.currentTimeMillis(); Collection rmqMsgs = new ArrayList<>(); - org.apache.rocketmq.common.message.Message rocketMsg; - for (Message msg:messages) { + for (Message msg : messages) { if (Objects.isNull(msg) || Objects.isNull(msg.getPayload())) { log.warn("Found a message empty in the batch, skip it"); continue; } - rocketMsg = RocketMQUtil.convertToRocketMessage(objectMapper, charset, destination, msg); - rmqMsgs.add(rocketMsg); + rmqMsgs.add(this.createRocketMqMessage(destination, msg)); } SendResult sendResult = producer.send(rmqMsgs, timeout); long costTime = System.currentTimeMillis() - now; - log.debug("send messages cost: {} ms, msgId:{}", costTime, sendResult.getMsgId()); + if (log.isDebugEnabled()) { + log.debug("send messages cost: {} ms, msgId:{}", costTime, sendResult.getMsgId()); + } return sendResult; } catch (Exception e) { log.error("syncSend with batch failed. destination:{}, messages.size:{} ", destination, messages.size()); @@ -167,9 +536,9 @@ public SendResult syncSend(String destination, Collection> messages, * Same to {@link #syncSend(String, Message)} with send timeout specified in addition. * * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} - * @param timeout send timeout with millis - * @param delayLevel level for the delay message + * @param message {@link org.springframework.messaging.Message} + * @param timeout send timeout with millis + * @param delayLevel level for the delay message * @return {@link SendResult} */ public SendResult syncSend(String destination, Message message, long timeout, int delayLevel) { @@ -177,17 +546,17 @@ public SendResult syncSend(String destination, Message message, long timeout, log.error("syncSend failed. destination:{}, message is null ", destination); throw new IllegalArgumentException("`message` and `message.payload` cannot be null"); } - try { long now = System.currentTimeMillis(); - org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(objectMapper, - charset, destination, message); + org.apache.rocketmq.common.message.Message rocketMsg = this.createRocketMqMessage(destination, message); if (delayLevel > 0) { rocketMsg.setDelayTimeLevel(delayLevel); } SendResult sendResult = producer.send(rocketMsg, timeout); long costTime = System.currentTimeMillis() - now; - log.debug("send message cost: {} ms, msgId:{}", costTime, sendResult.getMsgId()); + if (log.isDebugEnabled()) { + log.debug("send message cost: {} ms, msgId:{}", costTime, sendResult.getMsgId()); + } return sendResult; } catch (Exception e) { log.error("syncSend failed. destination:{}, message:{} ", destination, message); @@ -199,7 +568,7 @@ public SendResult syncSend(String destination, Message message, long timeout, * Same to {@link #syncSend(String, Message)}. * * @param destination formats: `topicName:tags` - * @param payload the Object to use as payload + * @param payload the Object to use as payload * @return {@link SendResult} */ public SendResult syncSend(String destination, Object payload) { @@ -210,12 +579,12 @@ public SendResult syncSend(String destination, Object payload) { * Same to {@link #syncSend(String, Object)} with send timeout specified in addition. * * @param destination formats: `topicName:tags` - * @param payload the Object to use as payload - * @param timeout send timeout with millis + * @param payload the Object to use as payload + * @param timeout send timeout with millis * @return {@link SendResult} */ public SendResult syncSend(String destination, Object payload, long timeout) { - Message message = this.doConvert(payload, null, null); + Message message = MessageBuilder.withPayload(payload).build(); return syncSend(destination, message, timeout); } @@ -223,8 +592,8 @@ public SendResult syncSend(String destination, Object payload, long timeout) { * Same to {@link #syncSend(String, Message)} with send orderly with hashKey by specified. * * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} - * @param hashKey use this key to select queue. for example: orderId, productId ... + * @param message {@link org.springframework.messaging.Message} + * @param hashKey use this key to select queue. for example: orderId, productId ... * @return {@link SendResult} */ public SendResult syncSendOrderly(String destination, Message message, String hashKey) { @@ -235,9 +604,9 @@ public SendResult syncSendOrderly(String destination, Message message, String * Same to {@link #syncSendOrderly(String, Message, String)} with send timeout specified in addition. * * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} - * @param hashKey use this key to select queue. for example: orderId, productId ... - * @param timeout send timeout with millis + * @param message {@link org.springframework.messaging.Message} + * @param hashKey use this key to select queue. for example: orderId, productId ... + * @param timeout send timeout with millis * @return {@link SendResult} */ public SendResult syncSendOrderly(String destination, Message message, String hashKey, long timeout) { @@ -245,14 +614,14 @@ public SendResult syncSendOrderly(String destination, Message message, String log.error("syncSendOrderly failed. destination:{}, message is null ", destination); throw new IllegalArgumentException("`message` and `message.payload` cannot be null"); } - try { long now = System.currentTimeMillis(); - org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(objectMapper, - charset, destination, message); + org.apache.rocketmq.common.message.Message rocketMsg = this.createRocketMqMessage(destination, message); SendResult sendResult = producer.send(rocketMsg, messageQueueSelector, hashKey, timeout); long costTime = System.currentTimeMillis() - now; - log.debug("send message cost: {} ms, msgId:{}", costTime, sendResult.getMsgId()); + if (log.isDebugEnabled()) { + log.debug("send message cost: {} ms, msgId:{}", costTime, sendResult.getMsgId()); + } return sendResult; } catch (Exception e) { log.error("syncSendOrderly failed. destination:{}, message:{} ", destination, message); @@ -264,8 +633,8 @@ public SendResult syncSendOrderly(String destination, Message message, String * Same to {@link #syncSend(String, Object)} with send orderly with hashKey by specified. * * @param destination formats: `topicName:tags` - * @param payload the Object to use as payload - * @param hashKey use this key to select queue. for example: orderId, productId ... + * @param payload the Object to use as payload + * @param hashKey use this key to select queue. for example: orderId, productId ... * @return {@link SendResult} */ public SendResult syncSendOrderly(String destination, Object payload, String hashKey) { @@ -276,33 +645,34 @@ public SendResult syncSendOrderly(String destination, Object payload, String has * Same to {@link #syncSendOrderly(String, Object, String)} with send timeout specified in addition. * * @param destination formats: `topicName:tags` - * @param payload the Object to use as payload - * @param hashKey use this key to select queue. for example: orderId, productId ... - * @param timeout send timeout with millis + * @param payload the Object to use as payload + * @param hashKey use this key to select queue. for example: orderId, productId ... + * @param timeout send timeout with millis * @return {@link SendResult} */ public SendResult syncSendOrderly(String destination, Object payload, String hashKey, long timeout) { - Message message = this.doConvert(payload, null, null); - return syncSendOrderly(destination, message, hashKey, producer.getSendMsgTimeout()); + Message message = MessageBuilder.withPayload(payload).build(); + return syncSendOrderly(destination, message, hashKey, timeout); } + /** - * Same to {@link #asyncSend(String, Message, SendCallback)} with send timeout and delay level specified in addition. + * Same to {@link #asyncSend(String, Message, SendCallback)} with send timeout and delay level specified in + * addition. * - * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} * @param sendCallback {@link SendCallback} - * @param timeout send timeout with millis - * @param delayLevel level for the delay message + * @param timeout send timeout with millis + * @param delayLevel level for the delay message */ - public void asyncSend(String destination, Message message, SendCallback sendCallback, long timeout, int delayLevel) { + public void asyncSend(String destination, Message message, SendCallback sendCallback, long timeout, + int delayLevel) { if (Objects.isNull(message) || Objects.isNull(message.getPayload())) { log.error("asyncSend failed. destination:{}, message is null ", destination); throw new IllegalArgumentException("`message` and `message.payload` cannot be null"); } - try { - org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(objectMapper, - charset, destination, message); + org.apache.rocketmq.common.message.Message rocketMsg = this.createRocketMqMessage(destination, message); if (delayLevel > 0) { rocketMsg.setDelayTimeLevel(delayLevel); } @@ -312,21 +682,22 @@ public void asyncSend(String destination, Message message, SendCallback sendC throw new MessagingException(e.getMessage(), e); } } + /** * Same to {@link #asyncSend(String, Message, SendCallback)} with send timeout specified in addition. * - * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} * @param sendCallback {@link SendCallback} - * @param timeout send timeout with millis + * @param timeout send timeout with millis */ public void asyncSend(String destination, Message message, SendCallback sendCallback, long timeout) { - asyncSend(destination,message,sendCallback,timeout,0); + asyncSend(destination, message, sendCallback, timeout, 0); } /** - *

Send message to broker asynchronously. asynchronous transmission is generally used in response time sensitive - * business scenarios.

+ *

Send message to broker asynchronously. asynchronous transmission is generally used in response time + * sensitive business scenarios.

*

* This method returns immediately. On sending completion, sendCallback will be executed. *

@@ -334,8 +705,8 @@ public void asyncSend(String destination, Message message, SendCallback sendC * DefaultMQProducer#getRetryTimesWhenSendAsyncFailed} times before claiming sending failure, which may yield * message duplication and application developers are the one to resolve this potential issue. * - * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} * @param sendCallback {@link SendCallback} */ public void asyncSend(String destination, Message message, SendCallback sendCallback) { @@ -345,21 +716,21 @@ public void asyncSend(String destination, Message message, SendCallback sendC /** * Same to {@link #asyncSend(String, Object, SendCallback)} with send timeout specified in addition. * - * @param destination formats: `topicName:tags` - * @param payload the Object to use as payload + * @param destination formats: `topicName:tags` + * @param payload the Object to use as payload * @param sendCallback {@link SendCallback} - * @param timeout send timeout with millis + * @param timeout send timeout with millis */ public void asyncSend(String destination, Object payload, SendCallback sendCallback, long timeout) { - Message message = this.doConvert(payload, null, null); + Message message = MessageBuilder.withPayload(payload).build(); asyncSend(destination, message, sendCallback, timeout); } /** * Same to {@link #asyncSend(String, Message, SendCallback)}. * - * @param destination formats: `topicName:tags` - * @param payload the Object to use as payload + * @param destination formats: `topicName:tags` + * @param payload the Object to use as payload * @param sendCallback {@link SendCallback} */ public void asyncSend(String destination, Object payload, SendCallback sendCallback) { @@ -370,22 +741,20 @@ public void asyncSend(String destination, Object payload, SendCallback sendCallb * Same to {@link #asyncSendOrderly(String, Message, String, SendCallback)} with send timeout specified in * addition. * - * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} - * @param hashKey use this key to select queue. for example: orderId, productId ... + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} + * @param hashKey use this key to select queue. for example: orderId, productId ... * @param sendCallback {@link SendCallback} - * @param timeout send timeout with millis + * @param timeout send timeout with millis */ public void asyncSendOrderly(String destination, Message message, String hashKey, SendCallback sendCallback, - long timeout) { + long timeout) { if (Objects.isNull(message) || Objects.isNull(message.getPayload())) { log.error("asyncSendOrderly failed. destination:{}, message is null ", destination); throw new IllegalArgumentException("`message` and `message.payload` cannot be null"); } - try { - org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(objectMapper, - charset, destination, message); + org.apache.rocketmq.common.message.Message rocketMsg = this.createRocketMqMessage(destination, message); producer.send(rocketMsg, messageQueueSelector, hashKey, sendCallback, timeout); } catch (Exception e) { log.error("asyncSendOrderly failed. destination:{}, message:{} ", destination, message); @@ -396,9 +765,9 @@ public void asyncSendOrderly(String destination, Message message, String hash /** * Same to {@link #asyncSend(String, Message, SendCallback)} with send orderly with hashKey by specified. * - * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} - * @param hashKey use this key to select queue. for example: orderId, productId ... + * @param destination formats: `topicName:tags` + * @param message {@link org.springframework.messaging.Message} + * @param hashKey use this key to select queue. for example: orderId, productId ... * @param sendCallback {@link SendCallback} */ public void asyncSendOrderly(String destination, Message message, String hashKey, SendCallback sendCallback) { @@ -408,9 +777,9 @@ public void asyncSendOrderly(String destination, Message message, String hash /** * Same to {@link #asyncSendOrderly(String, Message, String, SendCallback)}. * - * @param destination formats: `topicName:tags` - * @param payload the Object to use as payload - * @param hashKey use this key to select queue. for example: orderId, productId ... + * @param destination formats: `topicName:tags` + * @param payload the Object to use as payload + * @param hashKey use this key to select queue. for example: orderId, productId ... * @param sendCallback {@link SendCallback} */ public void asyncSendOrderly(String destination, Object payload, String hashKey, SendCallback sendCallback) { @@ -420,15 +789,15 @@ public void asyncSendOrderly(String destination, Object payload, String hashKey, /** * Same to {@link #asyncSendOrderly(String, Object, String, SendCallback)} with send timeout specified in addition. * - * @param destination formats: `topicName:tags` - * @param payload the Object to use as payload - * @param hashKey use this key to select queue. for example: orderId, productId ... + * @param destination formats: `topicName:tags` + * @param payload the Object to use as payload + * @param hashKey use this key to select queue. for example: orderId, productId ... * @param sendCallback {@link SendCallback} - * @param timeout send timeout with millis + * @param timeout send timeout with millis */ public void asyncSendOrderly(String destination, Object payload, String hashKey, SendCallback sendCallback, - long timeout) { - Message message = this.doConvert(payload, null, null); + long timeout) { + Message message = MessageBuilder.withPayload(payload).build(); asyncSendOrderly(destination, message, hashKey, sendCallback, timeout); } @@ -439,17 +808,15 @@ public void asyncSendOrderly(String destination, Object payload, String hashKey, * One-way transmission is used for cases requiring moderate reliability, such as log collection. * * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} + * @param message {@link org.springframework.messaging.Message} */ public void sendOneWay(String destination, Message message) { if (Objects.isNull(message) || Objects.isNull(message.getPayload())) { log.error("sendOneWay failed. destination:{}, message is null ", destination); throw new IllegalArgumentException("`message` and `message.payload` cannot be null"); } - try { - org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(objectMapper, - charset, destination, message); + org.apache.rocketmq.common.message.Message rocketMsg = this.createRocketMqMessage(destination, message); producer.sendOneway(rocketMsg); } catch (Exception e) { log.error("sendOneWay failed. destination:{}, message:{} ", destination, message); @@ -461,10 +828,10 @@ public void sendOneWay(String destination, Message message) { * Same to {@link #sendOneWay(String, Message)} * * @param destination formats: `topicName:tags` - * @param payload the Object to use as payload + * @param payload the Object to use as payload */ public void sendOneWay(String destination, Object payload) { - Message message = this.doConvert(payload, null, null); + Message message = MessageBuilder.withPayload(payload).build(); sendOneWay(destination, message); } @@ -472,18 +839,16 @@ public void sendOneWay(String destination, Object payload) { * Same to {@link #sendOneWay(String, Message)} with send orderly with hashKey by specified. * * @param destination formats: `topicName:tags` - * @param message {@link org.springframework.messaging.Message} - * @param hashKey use this key to select queue. for example: orderId, productId ... + * @param message {@link org.springframework.messaging.Message} + * @param hashKey use this key to select queue. for example: orderId, productId ... */ public void sendOneWayOrderly(String destination, Message message, String hashKey) { if (Objects.isNull(message) || Objects.isNull(message.getPayload())) { log.error("sendOneWayOrderly failed. destination:{}, message is null ", destination); throw new IllegalArgumentException("`message` and `message.payload` cannot be null"); } - try { - org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(objectMapper, - charset, destination, message); + org.apache.rocketmq.common.message.Message rocketMsg = this.createRocketMqMessage(destination, message); producer.sendOneway(rocketMsg, messageQueueSelector, hashKey); } catch (Exception e) { log.error("sendOneWayOrderly failed. destination:{}, message:{}", destination, message); @@ -495,10 +860,10 @@ public void sendOneWayOrderly(String destination, Message message, String has * Same to {@link #sendOneWayOrderly(String, Message, String)} * * @param destination formats: `topicName:tags` - * @param payload the Object to use as payload + * @param payload the Object to use as payload */ public void sendOneWayOrderly(String destination, Object payload, String hashKey) { - Message message = this.doConvert(payload, null, null); + Message message = MessageBuilder.withPayload(payload).build(); sendOneWayOrderly(destination, message, hashKey); } @@ -507,42 +872,29 @@ public void afterPropertiesSet() throws Exception { if (producer != null) { producer.start(); } + if (Objects.nonNull(consumer)) { + try { + consumer.start(); + } catch (Exception e) { + log.error("Failed to startup PullConsumer for RocketMQTemplate", e); + } + } } @Override protected void doSend(String destination, Message message) { SendResult sendResult = syncSend(destination, message); - log.debug("send message to `{}` finished. result:{}", destination, sendResult); + if (log.isDebugEnabled()) { + log.debug("send message to `{}` finished. result:{}", destination, sendResult); + } } - - @Override protected Message doConvert(Object payload, Map headers, MessagePostProcessor postProcessor) { - String content; - if (payload instanceof String) { - content = (String) payload; - } else { - // If payload not as string, use objectMapper change it. - try { - content = objectMapper.writeValueAsString(payload); - } catch (JsonProcessingException e) { - log.error("convert payload to String failed. payload:{}", payload); - throw new RuntimeException("convert to payload to String failed.", e); - } - } - - MessageBuilder builder = MessageBuilder.withPayload(content); - if (headers != null) { - builder.copyHeaders(headers); - } + Message message = super.doConvert(payload, headers, postProcessor); + MessageBuilder builder = MessageBuilder.fromMessage(message); builder.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN); - - Message message = builder.build(); - if (postProcessor != null) { - message = postProcessor.postProcessMessage(message); - } - return message; + return builder.build(); } @Override @@ -550,128 +902,139 @@ public void destroy() { if (Objects.nonNull(producer)) { producer.shutdown(); } - - for (Map.Entry kv : cache.entrySet()) { - if (Objects.nonNull(kv.getValue())) { - kv.getValue().shutdown(); - } + if (Objects.nonNull(consumer)) { + consumer.shutdown(); } - cache.clear(); - } - - private String getTxProducerGroupName(String name) { - return name == null ? RocketMQConfigUtils.ROCKETMQ_TRANSACTION_DEFAULT_GLOBAL_NAME : name; - } - - private TransactionMQProducer stageMQProducer(String name) throws MessagingException { - name = getTxProducerGroupName(name); - - TransactionMQProducer cachedProducer = cache.get(name); - if (cachedProducer == null) { - throw new MessagingException( - String.format("Can not found MQProducer '%s' in cache! please define @RocketMQLocalTransactionListener class or invoke createOrGetStartedTransactionMQProducer() to create it firstly", name)); - } - - return cachedProducer; } /** * Send Spring Message in Transaction * - * @param txProducerGroup the validate txProducerGroup name, set null if using the default name - * @param destination destination formats: `topicName:tags` - * @param message message {@link org.springframework.messaging.Message} - * @param arg ext arg + * @param destination destination formats: `topicName:tags` + * @param message message {@link org.springframework.messaging.Message} + * @param arg ext arg * @return TransactionSendResult * @throws MessagingException */ - public TransactionSendResult sendMessageInTransaction(final String txProducerGroup, final String destination, final Message message, final Object arg) throws MessagingException { + public TransactionSendResult sendMessageInTransaction(final String destination, + final Message message, final Object arg) throws MessagingException { try { - TransactionMQProducer txProducer = this.stageMQProducer(txProducerGroup); - org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(objectMapper, - charset, destination, message); - return txProducer.sendMessageInTransaction(rocketMsg, arg); + if (((TransactionMQProducer) producer).getTransactionListener() == null) { + throw new IllegalStateException("The rocketMQTemplate does not exist TransactionListener"); + } + org.apache.rocketmq.common.message.Message rocketMsg = this.createRocketMqMessage(destination, message); + return producer.sendMessageInTransaction(rocketMsg, arg); } catch (MQClientException e) { throw RocketMQUtil.convert(e); } } + private org.apache.rocketmq.common.message.Message createRocketMqMessage( + String destination, Message message) { + Message msg = this.doConvert(message.getPayload(), message.getHeaders(), null); + return RocketMQUtil.convertToRocketMessage(getMessageConverter(), charset, + destination, msg); + } + + private Object doConvertMessage(MessageExt messageExt, Type type) { + if (Objects.equals(type, MessageExt.class)) { + return messageExt; + } else if (Objects.equals(type, byte[].class)) { + return messageExt.getBody(); + } else { + String str = new String(messageExt.getBody(), Charset.forName(charset)); + if (Objects.equals(type, String.class)) { + return str; + } else { + // If msgType not string, use objectMapper change it. + try { + if (type instanceof Class) { + //if the messageType has not Generic Parameter + return this.getMessageConverter().fromMessage(MessageBuilder.withPayload(str).build(), (Class) type); + } else { + //if the messageType has Generic Parameter, then use SmartMessageConverter#fromMessage with third parameter "conversionHint". + //we have validate the MessageConverter is SmartMessageConverter in this#getMethodParameter. + return ((SmartMessageConverter) this.getMessageConverter()).fromMessage(MessageBuilder.withPayload(str).build(), (Class) ((ParameterizedType) type).getRawType(), null); + } + } catch (Exception e) { + log.error("convert failed. str:{}, msgType:{}", str, type); + throw new RuntimeException("cannot convert message to " + type, e); + } + } + } + } + + private Type getMessageType(RocketMQLocalRequestCallback rocketMQLocalRequestCallback) { + Class targetClass = AopProxyUtils.ultimateTargetClass(rocketMQLocalRequestCallback); + Type matchedGenericInterface = null; + while (Objects.nonNull(targetClass)) { + Type[] interfaces = targetClass.getGenericInterfaces(); + if (Objects.nonNull(interfaces)) { + for (Type type : interfaces) { + if (type instanceof ParameterizedType && (Objects.equals(((ParameterizedType) type).getRawType(), RocketMQLocalRequestCallback.class))) { + matchedGenericInterface = type; + break; + } + } + } + targetClass = targetClass.getSuperclass(); + } + if (Objects.isNull(matchedGenericInterface)) { + return Object.class; + } + + Type[] actualTypeArguments = ((ParameterizedType) matchedGenericInterface).getActualTypeArguments(); + if (Objects.nonNull(actualTypeArguments) && actualTypeArguments.length > 0) { + return actualTypeArguments[0]; + } + return Object.class; + } + /** - * Remove a TransactionMQProducer from cache by manual. - *

Note: RocketMQTemplate can release all cached producers when bean destroying, it is not recommended to directly - * use this method by user. + * receive message in pull mode. * - * @param txProducerGroup - * @throws MessagingException + * @param clazz message object type + * @param + * @return message list */ - public void removeTransactionMQProducer(String txProducerGroup) throws MessagingException { - txProducerGroup = getTxProducerGroupName(txProducerGroup); - if (cache.containsKey(txProducerGroup)) { - DefaultMQProducer cachedProducer = cache.get(txProducerGroup); - cachedProducer.shutdown(); - cache.remove(txProducerGroup); - } + public List receive(Class clazz) { + return receive(clazz, this.consumer.getPollTimeoutMillis()); } /** - * Create and start a transaction MQProducer, this new producer is cached in memory. - *

Note: This method is invoked internally when processing {@code @RocketMQLocalTransactionListener}, it is not - * recommended to directly use this method by user. + * Same to {@link #receive(Class)} with receive timeout specified in addition. * - * @param txProducerGroup Producer (group) name, unique for each producer - * @param transactionListener TransactoinListener impl class - * @param executorService Nullable. - * @param rpcHook Nullable. - * @return true if producer is created and started; false if the named producer already exists in cache. - * @throws MessagingException + * @param clazz message object type + * @param timeout receive timeout with millis + * @param + * @return message list */ - public boolean createAndStartTransactionMQProducer(String txProducerGroup, - RocketMQLocalTransactionListener transactionListener, - ExecutorService executorService, RPCHook rpcHook) throws MessagingException { - txProducerGroup = getTxProducerGroupName(txProducerGroup); - if (cache.containsKey(txProducerGroup)) { - log.info(String.format("get TransactionMQProducer '%s' from cache", txProducerGroup)); - return false; - } - - TransactionMQProducer txProducer = createTransactionMQProducer(txProducerGroup, transactionListener, executorService, rpcHook); - try { - txProducer.start(); - cache.put(txProducerGroup, txProducer); - } catch (MQClientException e) { - throw RocketMQUtil.convert(e); + public List receive(Class clazz, long timeout) { + List messageExts = this.consumer.poll(timeout); + List list = new ArrayList<>(messageExts.size()); + for (MessageExt messageExt : messageExts) { + list.add(doConvertMessage(messageExt, clazz)); } - - return true; + return list; } - private TransactionMQProducer createTransactionMQProducer(String name, - RocketMQLocalTransactionListener transactionListener, - ExecutorService executorService, RPCHook rpcHook) { - Assert.notNull(producer, "Property 'producer' is required"); - Assert.notNull(transactionListener, "Parameter 'transactionListener' is required"); - TransactionMQProducer txProducer; - if (Objects.nonNull(rpcHook)) { - txProducer = new TransactionMQProducer(name, rpcHook); - txProducer.setVipChannelEnabled(false); - txProducer.setInstanceName(RocketMQUtil.getInstanceName(rpcHook, name)); + @SuppressWarnings("unchecked") + private T doConvertMessage(MessageExt messageExt, Class messageType) { + if (Objects.equals(messageType, MessageExt.class)) { + return (T) messageExt; } else { - txProducer = new TransactionMQProducer(name); - } - txProducer.setTransactionListener(RocketMQUtil.convert(transactionListener)); - - txProducer.setNamesrvAddr(producer.getNamesrvAddr()); - if (executorService != null) { - txProducer.setExecutorService(executorService); + String str = new String(messageExt.getBody(), Charset.forName(charset)); + if (Objects.equals(messageType, String.class)) { + return (T) str; + } else { + // If msgType not string, use objectMapper change it. + try { + return (T) this.getMessageConverter().fromMessage(MessageBuilder.withPayload(str).build(), messageType); + } catch (Exception e) { + log.info("convert failed. str:{}, msgType:{}", str, messageType); + throw new RuntimeException("cannot convert message to " + messageType, e); + } + } } - - txProducer.setSendMsgTimeout(producer.getSendMsgTimeout()); - txProducer.setRetryTimesWhenSendFailed(producer.getRetryTimesWhenSendFailed()); - txProducer.setRetryTimesWhenSendAsyncFailed(producer.getRetryTimesWhenSendAsyncFailed()); - txProducer.setMaxMessageSize(producer.getMaxMessageSize()); - txProducer.setCompressMsgBodyOverHowmuch(producer.getCompressMsgBodyOverHowmuch()); - txProducer.setRetryAnotherBrokerWhenNotStoreOK(producer.isRetryAnotherBrokerWhenNotStoreOK()); - - return txProducer; } -} +} \ No newline at end of file diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/DefaultRocketMQListenerContainer.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/DefaultRocketMQListenerContainer.java index 6a730107..0b513a3f 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/DefaultRocketMQListenerContainer.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/DefaultRocketMQListenerContainer.java @@ -17,7 +17,12 @@ package org.apache.rocketmq.spring.support; -import com.fasterxml.jackson.databind.ObjectMapper; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Objects; import org.apache.rocketmq.client.AccessChannel; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.MessageSelector; @@ -29,14 +34,20 @@ import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.client.producer.SendCallback; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.client.producer.SendStatus; +import org.apache.rocketmq.client.utils.MessageUtil; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.RPCHook; +import org.apache.rocketmq.remoting.exception.RemotingException; import org.apache.rocketmq.spring.annotation.ConsumeMode; import org.apache.rocketmq.spring.annotation.MessageModel; import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.annotation.SelectorType; import org.apache.rocketmq.spring.core.RocketMQListener; import org.apache.rocketmq.spring.core.RocketMQPushConsumerLifecycleListener; +import org.apache.rocketmq.spring.core.RocketMQReplyListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.framework.AopProxyUtils; @@ -45,13 +56,15 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.SmartLifecycle; +import org.springframework.core.MethodParameter; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.converter.MessageConversionException; +import org.springframework.messaging.converter.MessageConverter; +import org.springframework.messaging.converter.SmartMessageConverter; +import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.Assert; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Objects; +import org.springframework.util.MimeTypeUtils; @SuppressWarnings("WeakerAccess") public class DefaultRocketMQListenerContainer implements InitializingBean, @@ -85,15 +98,19 @@ public class DefaultRocketMQListenerContainer implements InitializingBean, private String charset = "UTF-8"; - private ObjectMapper objectMapper; + private MessageConverter messageConverter; private RocketMQListener rocketMQListener; + private RocketMQReplyListener rocketMQReplyListener; + private RocketMQMessageListener rocketMQMessageListener; private DefaultMQPushConsumer consumer; - private Class messageType; + private Type messageType; + + private MethodParameter methodParameter; private boolean running; @@ -164,15 +181,15 @@ public void setCharset(String charset) { this.charset = charset; } - public ObjectMapper getObjectMapper() { - return objectMapper; + public MessageConverter getMessageConverter() { + return messageConverter; } - public void setObjectMapper(ObjectMapper objectMapper) { - this.objectMapper = objectMapper; + public DefaultRocketMQListenerContainer setMessageConverter(MessageConverter messageConverter) { + this.messageConverter = messageConverter; + return this; } - public RocketMQListener getRocketMQListener() { return rocketMQListener; } @@ -181,6 +198,14 @@ public void setRocketMQListener(RocketMQListener rocketMQListener) { this.rocketMQListener = rocketMQListener; } + public RocketMQReplyListener getRocketMQReplyListener() { + return rocketMQReplyListener; + } + + public void setRocketMQReplyListener(RocketMQReplyListener rocketMQReplyListener) { + this.rocketMQReplyListener = rocketMQReplyListener; + } + public RocketMQMessageListener getRocketMQMessageListener() { return rocketMQMessageListener; } @@ -191,8 +216,8 @@ public void setRocketMQMessageListener(RocketMQMessageListener anno) { this.consumeMode = anno.consumeMode(); this.consumeThreadMax = anno.consumeThreadMax(); this.messageModel = anno.messageModel(); - this.selectorExpression = anno.selectorExpression(); this.selectorType = anno.selectorType(); + this.selectorExpression = anno.selectorExpression(); this.consumeTimeout = anno.consumeTimeout(); } @@ -204,6 +229,10 @@ public SelectorType getSelectorType() { return selectorType; } + public void setSelectorExpression(String selectorExpression) { + this.selectorExpression = selectorExpression; + } + public String getSelectorExpression() { return selectorExpression; } @@ -220,11 +249,6 @@ public void setConsumer(DefaultMQPushConsumer consumer) { this.consumer = consumer; } - @Override - public void setupMessageListener(RocketMQListener rocketMQListener) { - this.rocketMQListener = rocketMQListener; - } - @Override public void destroy() { this.setRunning(false); @@ -287,13 +311,13 @@ public int getPhase() { return Integer.MAX_VALUE; } - @Override public void afterPropertiesSet() throws Exception { initRocketMQPushConsumer(); this.messageType = getMessageType(); - log.debug("RocketMQ messageType: {}", messageType.getName()); + this.methodParameter = getMethodParameter(); + log.debug("RocketMQ messageType: {}", messageType); } @Override @@ -327,11 +351,11 @@ public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeCo log.debug("received msg: {}", messageExt); try { long now = System.currentTimeMillis(); - rocketMQListener.onMessage(doConvertMessage(messageExt)); + handleMessage(messageExt); long costTime = System.currentTimeMillis() - now; log.debug("consume {} cost: {} ms", messageExt.getMsgId(), costTime); } catch (Exception e) { - log.warn("consume message failed. messageExt:{}", messageExt, e); + log.warn("consume message failed. messageId:{}, topic:{}, reconsumeTimes:{}", messageExt.getMsgId(), messageExt.getTopic(), messageExt.getReconsumeTimes(), e); context.setDelayLevelWhenNextConsume(delayLevelWhenNextConsume); return ConsumeConcurrentlyStatus.RECONSUME_LATER; } @@ -350,11 +374,11 @@ public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderly log.debug("received msg: {}", messageExt); try { long now = System.currentTimeMillis(); - rocketMQListener.onMessage(doConvertMessage(messageExt)); + handleMessage(messageExt); long costTime = System.currentTimeMillis() - now; - log.info("consume {} cost: {} ms", messageExt.getMsgId(), costTime); + log.debug("consume {} cost: {} ms", messageExt.getMsgId(), costTime); } catch (Exception e) { - log.warn("consume message failed. messageExt:{}", messageExt, e); + log.warn("consume message failed. messageId:{}, topic:{}, reconsumeTimes:{}", messageExt.getMsgId(), messageExt.getTopic(), messageExt.getReconsumeTimes(), e); context.setSuspendCurrentQueueTimeMillis(suspendCurrentQueueTimeMillis); return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; } @@ -364,6 +388,72 @@ public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderly } } + private void handleMessage( + MessageExt messageExt) throws MQClientException, RemotingException, InterruptedException { + if (rocketMQListener != null) { + rocketMQListener.onMessage(doConvertMessage(messageExt)); + } else if (rocketMQReplyListener != null) { + Object replyContent = rocketMQReplyListener.onMessage(doConvertMessage(messageExt)); + Message message = MessageBuilder.withPayload(replyContent).build(); + + org.apache.rocketmq.common.message.Message replyMessage = MessageUtil.createReplyMessage(messageExt, convertToBytes(message)); + consumer.getDefaultMQPushConsumerImpl().getmQClientFactory().getDefaultMQProducer().send(replyMessage, new SendCallback() { + @Override public void onSuccess(SendResult sendResult) { + if (sendResult.getSendStatus() != SendStatus.SEND_OK) { + log.error("Consumer replies message failed. SendStatus: {}", sendResult.getSendStatus()); + } else { + log.debug("Consumer replies message success."); + } + } + + @Override public void onException(Throwable e) { + log.error("Consumer replies message failed. error: {}", e.getLocalizedMessage()); + } + }); + } + } + + private byte[] convertToBytes(Message message) { + Message messageWithSerializedPayload = doConvert(message.getPayload(), message.getHeaders()); + Object payloadObj = messageWithSerializedPayload.getPayload(); + byte[] payloads; + try { + 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[]) messageWithSerializedPayload.getPayload(); + } else { + String jsonObj = (String) this.messageConverter.fromMessage(messageWithSerializedPayload, payloadObj.getClass()); + if (null == jsonObj) { + throw new RuntimeException(String.format( + "empty after conversion [messageConverter:%s,payloadClass:%s,payloadObj:%s]", + this.messageConverter.getClass(), payloadObj.getClass(), payloadObj)); + } + payloads = jsonObj.getBytes(Charset.forName(charset)); + } + } catch (Exception e) { + throw new RuntimeException("convert to bytes failed.", e); + } + return payloads; + } + + private Message doConvert(Object payload, MessageHeaders headers) { + Message message = this.messageConverter instanceof SmartMessageConverter ? + ((SmartMessageConverter) this.messageConverter).toMessage(payload, headers, null) : + this.messageConverter.toMessage(payload, headers); + if (message == null) { + String payloadType = payload.getClass().getName(); + Object contentType = headers != null ? headers.get(MessageHeaders.CONTENT_TYPE) : null; + throw new MessageConversionException("Unable to convert payload with type='" + payloadType + + "', contentType='" + contentType + "', converter=[" + this.messageConverter + "]"); + } + MessageBuilder builder = MessageBuilder.fromMessage(message); + builder.setHeaderIfAbsent(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.TEXT_PLAIN); + return builder.build(); + } @SuppressWarnings("unchecked") private Object doConvertMessage(MessageExt messageExt) { @@ -376,7 +466,14 @@ private Object doConvertMessage(MessageExt messageExt) { } else { // If msgType not string, use objectMapper change it. try { - return objectMapper.readValue(str, messageType); + if (messageType instanceof Class) { + //if the messageType has not Generic Parameter + return this.getMessageConverter().fromMessage(MessageBuilder.withPayload(str).build(), (Class) messageType); + } else { + //if the messageType has Generic Parameter, then use SmartMessageConverter#fromMessage with third parameter "conversionHint". + //we have validate the MessageConverter is SmartMessageConverter in this#getMethodParameter. + return ((SmartMessageConverter) this.getMessageConverter()).fromMessage(MessageBuilder.withPayload(str).build(), (Class) ((ParameterizedType) messageType).getRawType(), methodParameter); + } } catch (Exception e) { log.info("convert failed. str:{}, msgType:{}", str, messageType); throw new RuntimeException("cannot convert message to " + messageType, e); @@ -385,37 +482,67 @@ private Object doConvertMessage(MessageExt messageExt) { } } - private Class getMessageType() { - Class targetClass = AopProxyUtils.ultimateTargetClass(rocketMQListener); - Type[] interfaces = targetClass.getGenericInterfaces(); - Class superclass = targetClass.getSuperclass(); - while ((Objects.isNull(interfaces) || 0 == interfaces.length) && Objects.nonNull(superclass)) { - interfaces = superclass.getGenericInterfaces(); - superclass = targetClass.getSuperclass(); + private MethodParameter getMethodParameter() { + Class targetClass; + if (rocketMQListener != null) { + targetClass = AopProxyUtils.ultimateTargetClass(rocketMQListener); + } else { + targetClass = AopProxyUtils.ultimateTargetClass(rocketMQReplyListener); + } + Type messageType = this.getMessageType(); + Class clazz = null; + if (messageType instanceof ParameterizedType && messageConverter instanceof SmartMessageConverter) { + clazz = (Class) ((ParameterizedType) messageType).getRawType(); + } else if (messageType instanceof Class) { + clazz = (Class) messageType; + } else { + throw new RuntimeException("parameterType:" + messageType + " of onMessage method is not supported"); + } + try { + final Method method = targetClass.getMethod("onMessage", clazz); + return new MethodParameter(method, 0); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + throw new RuntimeException("parameterType:" + messageType + " of onMessage method is not supported"); + } + } + + private Type getMessageType() { + Class targetClass; + if (rocketMQListener != null) { + targetClass = AopProxyUtils.ultimateTargetClass(rocketMQListener); + } else { + targetClass = AopProxyUtils.ultimateTargetClass(rocketMQReplyListener); } - if (Objects.nonNull(interfaces)) { - for (Type type : interfaces) { - if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - if (Objects.equals(parameterizedType.getRawType(), RocketMQListener.class)) { - Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - if (Objects.nonNull(actualTypeArguments) && actualTypeArguments.length > 0) { - return (Class) actualTypeArguments[0]; - } else { - return Object.class; - } + Type matchedGenericInterface = null; + while (Objects.nonNull(targetClass)) { + Type[] interfaces = targetClass.getGenericInterfaces(); + if (Objects.nonNull(interfaces)) { + for (Type type : interfaces) { + if (type instanceof ParameterizedType && + (Objects.equals(((ParameterizedType) type).getRawType(), RocketMQListener.class) || Objects.equals(((ParameterizedType) type).getRawType(), RocketMQReplyListener.class))) { + matchedGenericInterface = type; + break; } } } - - return Object.class; - } else { + targetClass = targetClass.getSuperclass(); + } + if (Objects.isNull(matchedGenericInterface)) { return Object.class; } + + Type[] actualTypeArguments = ((ParameterizedType) matchedGenericInterface).getActualTypeArguments(); + if (Objects.nonNull(actualTypeArguments) && actualTypeArguments.length > 0) { + return actualTypeArguments[0]; + } + return Object.class; } private void initRocketMQPushConsumer() throws MQClientException { - Assert.notNull(rocketMQListener, "Property 'rocketMQListener' is required"); + if (rocketMQListener == null && rocketMQReplyListener == null) { + throw new IllegalArgumentException("Property 'rocketMQListener' or 'rocketMQReplyListener' is required"); + } Assert.notNull(consumerGroup, "Property 'consumerGroup' is required"); Assert.notNull(nameServer, "Property 'nameServer' is required"); Assert.notNull(topic, "Property 'topic' is required"); @@ -428,13 +555,14 @@ private void initRocketMQPushConsumer() throws MQClientException { enableMsgTrace, this.applicationContext.getEnvironment(). resolveRequiredPlaceholders(this.rocketMQMessageListener.customizedTraceTopic())); consumer.setVipChannelEnabled(false); - consumer.setInstanceName(RocketMQUtil.getInstanceName(rpcHook, consumerGroup)); } else { log.debug("Access-key or secret-key not configure in " + this + "."); consumer = new DefaultMQPushConsumer(consumerGroup, enableMsgTrace, - this.applicationContext.getEnvironment(). + this.applicationContext.getEnvironment(). resolveRequiredPlaceholders(this.rocketMQMessageListener.customizedTraceTopic())); } + + consumer.setInstanceName(RocketMQUtil.getInstanceName(nameServer)); String customizedNameServer = this.applicationContext.getEnvironment().resolveRequiredPlaceholders(this.rocketMQMessageListener.nameServer()); if (customizedNameServer != null) { @@ -450,7 +578,6 @@ private void initRocketMQPushConsumer() throws MQClientException { consumer.setConsumeThreadMin(consumeThreadMax); } consumer.setConsumeTimeout(consumeTimeout); - consumer.setInstanceName(this.name); switch (messageModel) { case BROADCASTING: @@ -487,6 +614,8 @@ private void initRocketMQPushConsumer() throws MQClientException { if (rocketMQListener instanceof RocketMQPushConsumerLifecycleListener) { ((RocketMQPushConsumerLifecycleListener) rocketMQListener).prepareStart(consumer); + } else if (rocketMQReplyListener instanceof RocketMQPushConsumerLifecycleListener) { + ((RocketMQPushConsumerLifecycleListener) rocketMQReplyListener).prepareStart(consumer); } } diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/RocketMQListenerContainer.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/RocketMQListenerContainer.java index ee52de80..d9693bc8 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/RocketMQListenerContainer.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/RocketMQListenerContainer.java @@ -17,14 +17,8 @@ package org.apache.rocketmq.spring.support; -import org.apache.rocketmq.spring.core.RocketMQListener; import org.springframework.beans.factory.DisposableBean; public interface RocketMQListenerContainer extends DisposableBean { - /** - * Setup the message listener to use. Throws an {@link IllegalArgumentException} if that message listener type is - * not supported. - */ - void setupMessageListener(RocketMQListener messageListener); } diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/RocketMQMessageConverter.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/RocketMQMessageConverter.java new file mode 100644 index 00000000..51a67193 --- /dev/null +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/RocketMQMessageConverter.java @@ -0,0 +1,75 @@ +/* + * 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.spring.support; + +import java.util.ArrayList; +import java.util.List; +import org.springframework.messaging.converter.ByteArrayMessageConverter; +import org.springframework.messaging.converter.CompositeMessageConverter; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.converter.MessageConverter; +import org.springframework.messaging.converter.StringMessageConverter; +import org.springframework.util.ClassUtils; + +/** + * @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) { + messageConverters.add(new MappingJackson2MessageConverter()); + } + if (FASTJSON_PRESENT) { + try { + messageConverters.add( + (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 MessageConverter getMessageConverter() { + return messageConverter; + } + +} diff --git a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/RocketMQUtil.java b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/RocketMQUtil.java index dc354133..a891fa7b 100644 --- a/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/RocketMQUtil.java +++ b/rocketmq-spring-boot/src/main/java/org/apache/rocketmq/spring/support/RocketMQUtil.java @@ -19,23 +19,37 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.rocketmq.acl.common.AclClientRPCHook; import org.apache.rocketmq.acl.common.SessionCredentials; +import org.apache.rocketmq.client.AccessChannel; +import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; +import org.apache.rocketmq.client.consumer.MessageSelector; import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.LocalTransactionState; import org.apache.rocketmq.client.producer.TransactionListener; +import org.apache.rocketmq.client.producer.TransactionMQProducer; +import org.apache.rocketmq.client.trace.AsyncTraceDispatcher; +import org.apache.rocketmq.client.trace.TraceDispatcher; +import org.apache.rocketmq.client.trace.hook.SendMessageTraceHookImpl; +import org.apache.rocketmq.common.UtilAll; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageConst; import org.apache.rocketmq.common.message.MessageExt; import org.apache.rocketmq.remoting.RPCHook; -import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState; +import org.apache.rocketmq.spring.annotation.MessageModel; +import org.apache.rocketmq.spring.annotation.SelectorType; import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener; +import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.MessagingException; +import org.springframework.messaging.converter.MessageConverter; import org.springframework.messaging.support.MessageBuilder; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; import java.nio.charset.Charset; import java.util.Map; import java.util.Objects; @@ -70,7 +84,7 @@ private static LocalTransactionState convertLocalTransactionState(RocketMQLocalT } // Never happen - log.warn("Failed to covert enum type RocketMQLocalTransactionState.%s", state); + log.warn("Failed to covert enum type RocketMQLocalTransactionState {}.", state); return LocalTransactionState.UNKNOW; } @@ -104,7 +118,8 @@ private static void addUserProperties(Map properties, MessageBui if (!CollectionUtils.isEmpty(properties)) { properties.forEach((key, val) -> { if (!MessageConst.STRING_HASH_SET.contains(key) && !MessageHeaders.ID.equals(key) - && !MessageHeaders.TIMESTAMP.equals(key)) { + && !MessageHeaders.TIMESTAMP.equals(key) && + (!key.startsWith(RocketMQHeaders.PREFIX) || !MessageConst.STRING_HASH_SET.contains(key.replaceFirst("^" + RocketMQHeaders.PREFIX, "")))) { messageBuilder.setHeader(key, val); } }); @@ -124,9 +139,10 @@ public static org.springframework.messaging.Message convertToSpringMessage( return messageBuilder.build(); } + @Deprecated public static org.apache.rocketmq.common.message.Message convertToRocketMessage( ObjectMapper objectMapper, String charset, - String destination, org.springframework.messaging.Message message) { + String destination, org.springframework.messaging.Message message) { Object payloadObj = message.getPayload(); byte[] payloads; @@ -142,37 +158,45 @@ public static org.apache.rocketmq.common.message.Message convertToRocketMessage( throw new RuntimeException("convert to RocketMQ message failed.", e); } } + return getAndWrapMessage(destination, message.getHeaders(), payloads); + } + private static Message getAndWrapMessage(String destination, MessageHeaders headers, byte[] payloads) { + if (destination == null || destination.length() < 1) { + return null; + } + if (payloads == null || payloads.length < 1) { + return null; + } String[] tempArr = destination.split(":", 2); String topic = tempArr[0]; String tags = ""; if (tempArr.length > 1) { tags = tempArr[1]; } - - org.apache.rocketmq.common.message.Message rocketMsg = new org.apache.rocketmq.common.message.Message(topic, tags, payloads); - - MessageHeaders headers = message.getHeaders(); + Message rocketMsg = new Message(topic, tags, payloads); if (Objects.nonNull(headers) && !headers.isEmpty()) { Object keys = headers.get(RocketMQHeaders.KEYS); + // if headers not have 'KEYS', try add prefix when getting keys + if (StringUtils.isEmpty(keys)) { + keys = headers.get(toRocketHeaderKey(RocketMQHeaders.KEYS)); + } if (!StringUtils.isEmpty(keys)) { // if headers has 'KEYS', set rocketMQ message key rocketMsg.setKeys(keys.toString()); } - Object flagObj = headers.getOrDefault("FLAG", "0"); int flag = 0; try { flag = Integer.parseInt(flagObj.toString()); } catch (NumberFormatException e) { // Ignore it - log.info("flag must be integer, flagObj:{}", flagObj); + if (log.isInfoEnabled()) { + log.info("flag must be integer, flagObj:{}", flagObj); + } } rocketMsg.setFlag(flag); - Object waitStoreMsgOkObj = headers.getOrDefault("WAIT_STORE_MSG_OK", "true"); - boolean waitStoreMsgOK = Boolean.TRUE.equals(waitStoreMsgOkObj); - rocketMsg.setWaitStoreMsgOK(waitStoreMsgOK); - + rocketMsg.setWaitStoreMsgOK(Boolean.TRUE.equals(waitStoreMsgOkObj)); headers.entrySet().stream() .filter(entry -> !Objects.equals(entry.getKey(), "FLAG") && !Objects.equals(entry.getKey(), "WAIT_STORE_MSG_OK")) // exclude "FLAG", "WAIT_STORE_MSG_OK" @@ -183,10 +207,37 @@ public static org.apache.rocketmq.common.message.Message convertToRocketMessage( }); } - return rocketMsg; } + public static org.apache.rocketmq.common.message.Message convertToRocketMessage( + MessageConverter messageConverter, String charset, + String destination, org.springframework.messaging.Message message) { + Object payloadObj = message.getPayload(); + byte[] payloads; + try { + 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)); + } + } catch (Exception e) { + throw new RuntimeException("convert to RocketMQ message failed.", e); + } + return getAndWrapMessage(destination, message.getHeaders(), payloads); + } + public static RPCHook getRPCHookByAkSk(Environment env, String accessKeyOrExpr, String secretKeyOrExpr) { String ak, sk; try { @@ -203,13 +254,83 @@ public static RPCHook getRPCHookByAkSk(Environment env, String accessKeyOrExpr, return null; } - public static String getInstanceName(RPCHook rpcHook, String identify) { - String separator = "|"; + public static DefaultMQProducer createDefaultMQProducer(String groupName, String ak, String sk, + boolean isEnableMsgTrace, String customizedTraceTopic) { + + boolean isEnableAcl = !StringUtils.isEmpty(ak) && !StringUtils.isEmpty(sk); + DefaultMQProducer producer; + if (isEnableAcl) { + producer = new TransactionMQProducer(groupName, new AclClientRPCHook(new SessionCredentials(ak, sk))); + producer.setVipChannelEnabled(false); + } else { + producer = new TransactionMQProducer(groupName); + } + + if (isEnableMsgTrace) { + try { + AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(groupName, TraceDispatcher.Type.PRODUCE, customizedTraceTopic, isEnableAcl ? new AclClientRPCHook(new SessionCredentials(ak, sk)) : null); + dispatcher.setHostProducer(producer.getDefaultMQProducerImpl()); + Field field = DefaultMQProducer.class.getDeclaredField("traceDispatcher"); + field.setAccessible(true); + field.set(producer, dispatcher); + producer.getDefaultMQProducerImpl().registerSendMessageHook( + new SendMessageTraceHookImpl(dispatcher)); + } catch (Throwable e) { + log.error("system trace hook init failed ,maybe can't send msg trace data"); + } + } + + return producer; + } + + public static String getInstanceName(String identify) { + char separator = '@'; StringBuilder instanceName = new StringBuilder(); - SessionCredentials sessionCredentials = ((AclClientRPCHook) rpcHook).getSessionCredentials(); - instanceName.append(sessionCredentials.getAccessKey()) - .append(separator).append(sessionCredentials.getSecretKey()) - .append(separator).append(identify); + instanceName.append(identify) + .append(separator).append(UtilAll.getPid()); return instanceName.toString(); } + + public static DefaultLitePullConsumer createDefaultLitePullConsumer(String nameServer, String accessChannel, + String groupName, String topicName, MessageModel messageModel, SelectorType selectorType, + String selectorExpression, String ak, String sk, int pullBatchSize) + throws MQClientException { + DefaultLitePullConsumer litePullConsumer = null; + if (!StringUtils.isEmpty(ak) && !StringUtils.isEmpty(sk)) { + litePullConsumer = new DefaultLitePullConsumer(groupName, new AclClientRPCHook(new SessionCredentials(ak, sk))); + litePullConsumer.setVipChannelEnabled(false); + } else { + litePullConsumer = new DefaultLitePullConsumer(groupName); + } + litePullConsumer.setNamesrvAddr(nameServer); + litePullConsumer.setInstanceName(RocketMQUtil.getInstanceName(nameServer)); + litePullConsumer.setPullBatchSize(pullBatchSize); + if (accessChannel != null) { + litePullConsumer.setAccessChannel(AccessChannel.valueOf(accessChannel)); + } + + switch (messageModel) { + case BROADCASTING: + litePullConsumer.setMessageModel(org.apache.rocketmq.common.protocol.heartbeat.MessageModel.BROADCASTING); + break; + case CLUSTERING: + litePullConsumer.setMessageModel(org.apache.rocketmq.common.protocol.heartbeat.MessageModel.CLUSTERING); + break; + default: + throw new IllegalArgumentException("Property 'messageModel' was wrong."); + } + + switch (selectorType) { + case SQL92: + litePullConsumer.subscribe(topicName, MessageSelector.bySql(selectorExpression)); + break; + case TAG: + litePullConsumer.subscribe(topicName, selectorExpression); + break; + default: + throw new IllegalArgumentException("Property 'selectorType' was wrong."); + } + + return litePullConsumer; + } } diff --git a/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/autoconfigure/RocketMQAutoConfigurationTest.java b/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/autoconfigure/RocketMQAutoConfigurationTest.java index 528e9161..9f31b09b 100644 --- a/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/autoconfigure/RocketMQAutoConfigurationTest.java +++ b/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/autoconfigure/RocketMQAutoConfigurationTest.java @@ -17,30 +17,37 @@ package org.apache.rocketmq.spring.autoconfigure; -import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.ArrayList; +import java.util.List; + +import org.apache.rocketmq.client.consumer.DefaultLitePullConsumer; import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.spring.annotation.ExtRocketMQConsumerConfiguration; import org.apache.rocketmq.spring.annotation.ExtRocketMQTemplateConfiguration; import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; +import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener; import org.apache.rocketmq.spring.core.RocketMQListener; +import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener; +import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState; +import org.apache.rocketmq.spring.core.RocketMQReplyListener; import org.apache.rocketmq.spring.core.RocketMQTemplate; -import org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer; +import org.apache.rocketmq.spring.support.RocketMQMessageConverter; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.support.BeanDefinitionValidationException; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.assertj.AssertableApplicationContext; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.GenericMessage; import static org.assertj.core.api.Assertions.assertThat; public class RocketMQAutoConfigurationTest { private ApplicationContextRunner runner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RocketMQAutoConfiguration.class)); - + .withConfiguration(AutoConfigurations.of(RocketMQAutoConfiguration.class)); @Test(expected = NoSuchBeanDefinitionException.class) public void testDefaultMQProducerNotCreatedByDefault() { @@ -48,14 +55,32 @@ public void testDefaultMQProducerNotCreatedByDefault() { runner.run(context -> context.getBean(DefaultMQProducer.class)); } + @Test(expected = NoSuchBeanDefinitionException.class) + public void testDefaultLitePullConsumerNotCreatedByDefault() { + // You will see the WARN log message about missing rocketmq.name-server spring property when running this test case. + runner.run(context -> context.getBean(DefaultLitePullConsumer.class)); + } @Test public void testDefaultMQProducerWithRelaxPropertyName() { runner.withPropertyValues("rocketmq.nameServer=127.0.0.1:9876", - "rocketmq.producer.group=spring_rocketmq", + "rocketmq.producer.group=spring_rocketmq", + "rocketmq.accessChannel=LOCAL"). + run((context) -> { + assertThat(context).hasSingleBean(DefaultMQProducer.class); + assertThat(context).hasSingleBean(RocketMQProperties.class); + }); + + } + + @Test + public void testDefaultLitePullConsumerWithRelaxPropertyName() { + runner.withPropertyValues("rocketmq.nameServer=127.0.0.1:9876", + "rocketmq.consumer.group=spring_rocketmq", + "rocketmq.consumer.topic=test", "rocketmq.accessChannel=LOCAL"). run((context) -> { - assertThat(context).hasSingleBean(DefaultMQProducer.class); + assertThat(context).hasSingleBean(DefaultLitePullConsumer.class); assertThat(context).hasSingleBean(RocketMQProperties.class); }); @@ -64,7 +89,16 @@ public void testDefaultMQProducerWithRelaxPropertyName() { @Test public void testBadAccessChannelProperty() { runner.withPropertyValues("rocketmq.nameServer=127.0.0.1:9876", - "rocketmq.producer.group=spring_rocketmq", + "rocketmq.producer.group=spring_rocketmq", + "rocketmq.accessChannel=LOCAL123"). + run((context) -> { + //Should throw exception for bad accessChannel property + assertThat(context).getFailure(); + }); + + runner.withPropertyValues("rocketmq.nameServer=127.0.0.1:9876", + "rocketmq.consumer.group=spring_rocketmq", + "rocketmq.consumer.topic=test", "rocketmq.accessChannel=LOCAL123"). run((context) -> { //Should throw exception for bad accessChannel property @@ -75,74 +109,134 @@ public void testBadAccessChannelProperty() { @Test public void testDefaultMQProducer() { runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876", - "rocketmq.producer.group=spring_rocketmq"). + "rocketmq.producer.group=spring_rocketmq"). + run((context) -> { + assertThat(context).hasSingleBean(DefaultMQProducer.class); + }); + } + + @Test + public void testDefaultLitePullConsumer() { + runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876", + "rocketmq.consumer.group=spring_rocketmq", + "rocketmq.consumer.topic=test"). run((context) -> { - assertThat(context).hasSingleBean(DefaultMQProducer.class); + assertThat(context).hasSingleBean(DefaultLitePullConsumer.class); }); + } + @Test + public void testExtRocketMQTemplate() { + runner.withPropertyValues("rocketmq.name-server=127.0.1.1:9876"). + withUserConfiguration(TestExtRocketMQTemplateConfig.class, CustomObjectMappersConfig.class). + run((context) -> { + // No producer on consume side + assertThat(context).getBean("extRocketMQTemplate").hasFieldOrProperty("producer"); + // Auto-create consume container if existing Bean annotated with @RocketMQMessageListener + }); } + @Test - public void testRocketMQListenerContainer() { - runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876"). - withUserConfiguration(TestConfig.class). + public void testExtRocketMQConsumer() { + runner.withPropertyValues("rocketmq.name-server=127.0.1.1:9876"). + withUserConfiguration(TestExtRocketMQConsumerConfig.class, CustomObjectMappersConfig.class). run((context) -> { - // No producer on consume side - assertThat(context).doesNotHaveBean(DefaultMQProducer.class); - // Auto-create consume container if existing Bean annotated with @RocketMQMessageListener - assertThat(context).hasBean("org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer_1"); - assertThat(context).hasBean("org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer_2"); - assertThat(context).getBean("org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer_1"). - hasFieldOrPropertyWithValue("nameServer", "127.0.0.1:9876"); - assertThat(context).getBean("org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer_2"). - hasFieldOrPropertyWithValue("nameServer", "127.0.1.1:9876"); + assertThat(context).getBean("extRocketMQTemplate").hasFieldOrProperty("consumer"); }); + } + + @Test + public void testConsumerListener() { + runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876", + "rocketmq.producer.group=spring_rocketmq", + "rocketmq.consumer.listeners.spring_rocketmq.FOO_TEST_TOPIC=false", + "rocketmq.consumer.listeners.spring_rocketmq.FOO_TEST_TOPIC2=true"). + run((context) -> { + RocketMQProperties rocketMQProperties = context.getBean(RocketMQProperties.class); + assertThat(rocketMQProperties.getConsumer().getListeners().get("spring_rocketmq").get("FOO_TEST_TOPIC").booleanValue()).isEqualTo(false); + assertThat(rocketMQProperties.getConsumer().getListeners().get("spring_rocketmq").get("FOO_TEST_TOPIC2").booleanValue()).isEqualTo(true); + }); } @Test - public void testRocketMQListenerWithCustomObjectMapper() { - runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876"). - withUserConfiguration(TestConfig.class, CustomObjectMapperConfig.class). - run((context) -> { - assertThat(context.getBean("org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer_1", - DefaultRocketMQListenerContainer.class).getObjectMapper()) - .isSameAs(context.getBean(CustomObjectMapperConfig.class).testObjectMapper()); - }); + public void testRocketMQTransactionListener() { + runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876", + "rocketmq.producer.group=spring_rocketmq", + "demo.rocketmq.transaction.producer.group=transaction-group1"). + withUserConfiguration(TestTransactionListenerConfig.class). + run((context) -> { + assertThat(context).hasSingleBean(TestRocketMQLocalTransactionListener.class); + }); } @Test - public void testRocketMQListenerWithSeveralObjectMappers() { - runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876"). - withUserConfiguration(TestConfig.class, CustomObjectMappersConfig.class). - run((context) -> { - assertThat(context.getBean("org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer_1", - DefaultRocketMQListenerContainer.class).getObjectMapper()) - .isSameAs(context.getBean(CustomObjectMappersConfig.class).rocketMQMessageObjectMapper()); - }); + public void testBatchSendMessage() { + runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876", + "rocketmq.producer.group=spring_rocketmq"). + run((context) -> { + RocketMQTemplate rocketMQTemplate = context.getBean(RocketMQTemplate.class); + List> batchMessages = new ArrayList>(); + + String errorMsg = null; + try { + SendResult customSendResult = rocketMQTemplate.syncSend("test", batchMessages, 60000); + } catch (IllegalArgumentException e) { + // it will be throw IllegalArgumentException: `messages` can not be empty + errorMsg = e.getMessage(); + } + + // that means the rocketMQTemplate.syncSend is chosen the correct type method + Assert.assertEquals("`messages` can not be empty", errorMsg); + }); + } + public void testPlaceholdersListenerContainer() { + runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876", + "demo.placeholders.consumer.group = abc3", + "demo.placeholders.consumer.topic = test", + "demo.placeholders.consumer.tags = tag1"). + withUserConfiguration(TestPlaceholdersConfig.class). + run((context) -> { + // No producer on consume side + assertThat(context).doesNotHaveBean(DefaultMQProducer.class); + // Auto-create consume container if existing Bean annotated with @RocketMQMessageListener + assertThat(context).hasBean("org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer_1"); + assertThat(context).getBean("org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer_1"). + hasFieldOrPropertyWithValue("nameServer", "127.0.0.1:9876"). + hasFieldOrPropertyWithValue("consumerGroup", "abc3"). + hasFieldOrPropertyWithValue("topic", "test"). + hasFieldOrPropertyWithValue("selectorExpression", "tag1"); + }); + } @Test - public void testExtRocketMQTemplate() { + public void testRocketMQListenerContainer() { runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876"). - withUserConfiguration(ExtRocketMQTemplateConfig.class, CustomObjectMappersConfig.class). - run(new ContextConsumer() { - @Override - public void accept(AssertableApplicationContext context) throws Throwable { - Throwable th = context.getStartupFailure(); - System.out.printf("th==" + th + "\n"); - Assert.assertTrue(th instanceof BeanDefinitionValidationException); - } - }); + withUserConfiguration(TestConfig.class). + run((context) -> { + assertThat(context).getFailure().hasMessageContaining("connect to [127.0.0.1:9876] failed"); + }); + } - runner.withPropertyValues("rocketmq.name-server=127.0.1.1:9876"). - withUserConfiguration(ExtRocketMQTemplateConfig.class, CustomObjectMappersConfig.class). - run((context) -> { - // No producer on consume side - assertThat(context).getBean("extRocketMQTemplate").hasFieldOrProperty("producer"); - // Auto-create consume container if existing Bean annotated with @RocketMQMessageListener - }); + @Test + public void testRocketMQListenerContainer_RocketMQReplyListener() { + runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876"). + withUserConfiguration(TestConfigWithRocketMQReplyListener.class). + run((context) -> { + assertThat(context).getFailure().hasMessageContaining("connect to [127.0.0.1:9876] failed"); + }); + } + + @Test(expected = IllegalStateException.class) + public void testRocketMQListenerContainer_WrongRocketMQListenerType() { + runner.withPropertyValues("rocketmq.name-server=127.0.0.1:9876"). + withUserConfiguration(TestConfigWithWrongRocketMQListener.class). + run((context) -> { + context.getBean(RocketMQMessageConverter.class); + }); } @Configuration @@ -150,43 +244,62 @@ static class TestConfig { @Bean public Object consumeListener() { - return new MyMessageListener(); + return new TestDefaultNameServerListener(); } @Bean public Object consumeListener1() { - return new MyMessageListener1(); + return new TestCustomNameServerListener(); } } @Configuration - static class CustomObjectMapperConfig { + static class TestConfigWithRocketMQReplyListener { @Bean - public ObjectMapper testObjectMapper() { - return new ObjectMapper(); + public Object consumeListener() { + return new TestDefaultNameServerRocketMQReplyListener(); + } + + @Bean + public Object consumeListener1() { + return new TestCustomNameServerRocketMQReplyListener(); } } @Configuration - static class CustomObjectMappersConfig { + static class TestConfigWithWrongRocketMQListener { @Bean - public ObjectMapper testObjectMapper() { - return new ObjectMapper(); + public Object consumeListener() { + return new WrongRocketMQListener(); } + } + + @Configuration + static class CustomObjectMapperConfig { @Bean - public ObjectMapper rocketMQMessageObjectMapper() { - return new ObjectMapper(); + public RocketMQMessageConverter rocketMQMessageConverter() { + return new RocketMQMessageConverter(); + } + + } + + @Configuration + static class CustomObjectMappersConfig { + + @Bean + public RocketMQMessageConverter rocketMQMessageConverter() { + return new RocketMQMessageConverter(); } } @RocketMQMessageListener(consumerGroup = "abc", topic = "test") - static class MyMessageListener implements RocketMQListener { + static class TestDefaultNameServerListener implements RocketMQListener { @Override public void onMessage(Object message) { @@ -195,7 +308,7 @@ public void onMessage(Object message) { } @RocketMQMessageListener(nameServer = "127.0.1.1:9876", consumerGroup = "abc1", topic = "test") - static class MyMessageListener1 implements RocketMQListener { + static class TestCustomNameServerListener implements RocketMQListener { @Override public void onMessage(Object message) { @@ -203,18 +316,101 @@ public void onMessage(Object message) { } } + @RocketMQMessageListener(consumerGroup = "abcd", topic = "test") + static class TestDefaultNameServerRocketMQReplyListener implements RocketMQReplyListener { + + @Override + public String onMessage(String message) { + return "test"; + } + } + + @RocketMQMessageListener(consumerGroup = "abcde", topic = "test") + static class WrongRocketMQListener { + + public String onMessage(String message) { + return "test"; + } + } + + @RocketMQMessageListener(nameServer = "127.0.1.1:9876", consumerGroup = "abcd1", topic = "test") + static class TestCustomNameServerRocketMQReplyListener implements RocketMQReplyListener { + + @Override + public String onMessage(String message) { + return "test"; + } + } + + @Configuration + static class TestTransactionListenerConfig { + @Bean + public Object rocketMQLocalTransactionListener() { + return new TestRocketMQLocalTransactionListener(); + } + + } + + @RocketMQTransactionListener + static class TestRocketMQLocalTransactionListener implements RocketMQLocalTransactionListener { + + @Override + public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) { + return RocketMQLocalTransactionState.COMMIT; + } + + @Override + public RocketMQLocalTransactionState checkLocalTransaction(Message msg) { + return RocketMQLocalTransactionState.COMMIT; + } + } + @Configuration - static class ExtRocketMQTemplateConfig { + static class TestExtRocketMQTemplateConfig { @Bean public RocketMQTemplate extRocketMQTemplate() { - return new MyExtRocketMQTemplate(); + return new TestExtRocketMQTemplate(); } } @ExtRocketMQTemplateConfiguration(group = "test", nameServer = "127.0.0.1:9876") - static class MyExtRocketMQTemplate extends RocketMQTemplate { + static class TestExtRocketMQTemplate extends RocketMQTemplate { + + } + + @Configuration + static class TestPlaceholdersConfig { + + @Bean + public Object consumeListener() { + return new TestPlaceholdersListener(); + } + + } + + @RocketMQMessageListener(consumerGroup = "${demo.placeholders.consumer.group}", topic = "${demo.placeholders.consumer.topic}", selectorExpression = "${demo.placeholders.consumer.tags}") + static class TestPlaceholdersListener implements RocketMQListener { + + @Override + public void onMessage(Object message) { + + } + } + + @Configuration + static class TestExtRocketMQConsumerConfig { + + @Bean + public RocketMQTemplate extRocketMQTemplate() { + return new TestExtRocketMQConsumer(); + } + + } + + @ExtRocketMQConsumerConfiguration(topic = "test", group = "test", nameServer = "127.0.0.1:9876") + static class TestExtRocketMQConsumer extends RocketMQTemplate { } } diff --git a/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/core/ExtRocketMQTemplateTest.java b/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/core/ExtRocketMQTemplateTest.java new file mode 100644 index 00000000..dc88253a --- /dev/null +++ b/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/core/ExtRocketMQTemplateTest.java @@ -0,0 +1,113 @@ +/* + * 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.spring.core; + +import org.apache.rocketmq.client.producer.TransactionMQProducer; +import org.apache.rocketmq.spring.annotation.ExtRocketMQConsumerConfiguration; +import org.apache.rocketmq.spring.annotation.ExtRocketMQTemplateConfiguration; +import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener; +import org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import javax.annotation.Resource; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringJUnit4ClassRunner.class) + +@SpringBootTest(properties = { + "rocketmq.nameServer=127.0.0.1:9876", "rocketmq.producer.group=extRocketMQTemplate-test-producer_group"}, classes = {RocketMQAutoConfiguration.class, ExtRocketMQTemplate.class, ExtTransactionListenerImpl.class, ExtRocketMQConsumer.class}) +public class ExtRocketMQTemplateTest { + + @Resource(name = "extRocketMQTemplate") + private RocketMQTemplate extRocketMQTemplate; + + @Resource(name = "extRocketMQConsumer") + private ExtRocketMQConsumer extRocketMQConsumer; + + @Resource + private RocketMQTemplate rocketMQTemplate; + + @Test + public void testProperties() { + assertThat(extRocketMQTemplate.getProducer().getNamesrvAddr()).isEqualTo("172.0.0.1:9876"); + assertThat(extRocketMQTemplate.getProducer().getProducerGroup()).isEqualTo("extRocketMQTemplate-test-group"); + assertThat(extRocketMQTemplate.getProducer().getSendMsgTimeout()).isEqualTo(3000); + assertThat(extRocketMQTemplate.getProducer().getMaxMessageSize()).isEqualTo(4 * 1024); + + assertThat(extRocketMQConsumer.getConsumer().getNamesrvAddr()).isEqualTo("172.0.0.1:9876"); + assertThat(extRocketMQConsumer.getConsumer().getConsumerGroup()).isEqualTo("extRocketMQTemplate-test-group"); + assertThat(extRocketMQConsumer.getConsumer().getPullBatchSize()).isEqualTo(3); + } + + @Test + public void testTransactionListener() { + assertThat(((TransactionMQProducer) extRocketMQTemplate.getProducer()).getTransactionListener()).isNotNull(); + assertThat(((TransactionMQProducer) rocketMQTemplate.getProducer()).getTransactionListener()).isNull(); + } + + @Test + public void testSendTransactionalMessage() { + try { + rocketMQTemplate.sendMessageInTransaction("test-topic", MessageBuilder.withPayload("payload").build(), null); + } catch (IllegalStateException e) { + assertThat(e).hasMessageContaining("The rocketMQTemplate does not exist TransactionListener"); + } + + try { + extRocketMQTemplate.sendMessageInTransaction("test-topic", MessageBuilder.withPayload("payload").build(), null); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.client.exception.MQClientException: send message Exception"); + } + + } +} + +@ExtRocketMQTemplateConfiguration(nameServer = "172.0.0.1:9876", group = "extRocketMQTemplate-test-group", + sendMessageTimeout = 3000, maxMessageSize = 4 * 1024) +class ExtRocketMQTemplate extends RocketMQTemplate { + +} + +@RocketMQTransactionListener(rocketMQTemplateBeanName = "extRocketMQTemplate") +class ExtTransactionListenerImpl implements RocketMQLocalTransactionListener { + @Override + public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) { + return RocketMQLocalTransactionState.UNKNOWN; + } + + @Override + public RocketMQLocalTransactionState checkLocalTransaction(Message msg) { + return RocketMQLocalTransactionState.COMMIT; + } +} + +@ExtRocketMQConsumerConfiguration(nameServer = "172.0.0.1:9876", group = "extRocketMQTemplate-test-group", topic = "test", pullBatchSize = 3) +class ExtRocketMQConsumer extends RocketMQTemplate { + +} + + + + + diff --git a/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/core/RocketMQTemplateTest.java b/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/core/RocketMQTemplateTest.java new file mode 100644 index 00000000..2103da2d --- /dev/null +++ b/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/core/RocketMQTemplateTest.java @@ -0,0 +1,270 @@ +/* + * 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.spring.core; + +import javax.annotation.Resource; +import org.apache.rocketmq.client.AccessChannel; +import org.apache.rocketmq.client.producer.SendCallback; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.client.producer.TransactionMQProducer; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener; +import org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.messaging.MessagingException; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(properties = { + "rocketmq.nameServer=127.0.0.1:9876", "rocketmq.producer.group=rocketMQTemplate-test-producer_group", + "test.rocketmq.topic=test", "rocketmq.producer.access-key=test-ak", + "rocketmq.producer.secret-key=test-sk", "rocketmq.accessChannel=LOCAL", + "rocketmq.producer.sendMessageTimeout= 3500", "rocketmq.producer.retryTimesWhenSendFailed=3", + "rocketmq.producer.retryTimesWhenSendAsyncFailed=3", + "rocketmq.consumer.group=spring_rocketmq", "rocketmq.consumer.topic=test"}, classes = {RocketMQAutoConfiguration.class, TransactionListenerImpl.class}) + +public class RocketMQTemplateTest { + @Resource + RocketMQTemplate rocketMQTemplate; + + @Value("${test.rocketmq.topic}") + String topic; + + @Value("stringRequestTopic:tagA") + String stringRequestTopic; + + @Value("objectRequestTopic:tagA") + String objectRequestTopic; + + @Test + public void testSendMessage() { + try { + rocketMQTemplate.syncSend(topic, "payload"); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [127.0.0.1:9876] failed"); + } + + try { + rocketMQTemplate.asyncSend(topic, "payload", new SendCallback() { + @Override public void onSuccess(SendResult sendResult) { + + } + + @Override public void onException(Throwable e) { + + } + }); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [127.0.0.1:9876] failed"); + } + + try { + rocketMQTemplate.syncSendOrderly(topic, "payload", "hashkey"); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [127.0.0.1:9876] failed"); + } + } + + @Test + public void testReceiveMessage() { + try { + rocketMQTemplate.receive(String.class); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [127.0.0.1:9876] failed"); + } + } + + @Test + public void testSendMessage_withCustomAsyncSenderExecutor() { + ExecutorService executorService = new ThreadPoolExecutor( + 2, + 5, + 100, + TimeUnit.SECONDS, + new ArrayBlockingQueue(2000), + new ThreadFactory() { + private AtomicInteger threadIndex = new AtomicInteger(0); + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "AsyncSenderExecutor_" + this.threadIndex.incrementAndGet()); + } + }); + rocketMQTemplate.setAsyncSenderExecutor(executorService); + try { + rocketMQTemplate.asyncSend(topic, "payload", new SendCallback() { + @Override public void onSuccess(SendResult sendResult) { + + } + + @Override public void onException(Throwable e) { + + } + }); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [127.0.0.1:9876] failed"); + } + } + + @Test + public void testSendAndReceive_NullMessage() { + try { + String response = rocketMQTemplate.sendAndReceive(stringRequestTopic, new Message() { + @Override public String getPayload() { + return null; + } + + @Override public MessageHeaders getHeaders() { + return null; + } + }, String.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageContaining("`message` and `message.payload` cannot be null"); + } + + try { + String response = rocketMQTemplate.sendAndReceive(stringRequestTopic, (Object) null, String.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageContaining("Payload must not be null"); + } + } + + @Test + public void testSendAndReceive_Sync() throws InterruptedException { + try { + String responseMessage = rocketMQTemplate.sendAndReceive(stringRequestTopic, MessageBuilder.withPayload("requestTopicSync").build(), String.class); + assertThat(responseMessage).isNotNull(); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [127.0.0.1:9876] failed"); + } + + try { + String responseMessage = rocketMQTemplate.sendAndReceive(stringRequestTopic, "requestTopicSync", String.class, "orderId"); + assertThat(responseMessage).isNotNull(); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [127.0.0.1:9876] failed"); + } + } + + @Test + public void testSendAndReceive_Async() { + try { + rocketMQTemplate.sendAndReceive(stringRequestTopic, MessageBuilder.withPayload("requestTopicASync").build(), new RocketMQLocalRequestCallback() { + @Override public void onSuccess(String message) { + System.out.printf("receive string: %s %n", message); + } + + @Override public void onException(Throwable e) { + e.printStackTrace(); + } + }); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [127.0.0.1:9876] failed"); + } + + try { + rocketMQTemplate.sendAndReceive(stringRequestTopic, "requestTopicAsyncWithHasKey", new RocketMQLocalRequestCallback() { + @Override public void onSuccess(String message) { + System.out.printf("receive string: %s %n", message); + } + + @Override public void onException(Throwable e) { + e.printStackTrace(); + } + }, "order-id"); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [127.0.0.1:9876] failed"); + } + + try { + rocketMQTemplate.sendAndReceive(stringRequestTopic, "requestTopicAsyncWithTimeout", new RocketMQLocalRequestCallback() { + @Override public void onSuccess(String message) { + System.out.printf("receive string: %s %n", message); + } + + @Override public void onException(Throwable e) { + e.printStackTrace(); + } + }, "order-id", 5000); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [127.0.0.1:9876] failed"); + } + try { + rocketMQTemplate.sendAndReceive(objectRequestTopic, "requestTopicAsyncWithTimeout", new RocketMQLocalRequestCallback() { + @Override public void onSuccess(MessageExt message) { + System.out.printf("receive messageExt: %s %n", message.toString()); + } + + @Override public void onException(Throwable e) { + e.printStackTrace(); + } + }, 5000); + } catch (MessagingException e) { + assertThat(e).hasMessageContaining("org.apache.rocketmq.remoting.exception.RemotingConnectException: connect to [127.0.0.1:9876] failed"); + } + } + + @Test + public void testProperties() { + assertThat(rocketMQTemplate.getProducer().getNamesrvAddr()).isEqualTo("127.0.0.1:9876"); + assertThat(rocketMQTemplate.getProducer().getProducerGroup()).isEqualTo("rocketMQTemplate-test-producer_group"); + assertThat(rocketMQTemplate.getProducer().getAccessChannel()).isEqualTo(AccessChannel.LOCAL); + assertThat(rocketMQTemplate.getProducer().getSendMsgTimeout()).isEqualTo(3500); + assertThat(rocketMQTemplate.getProducer().getMaxMessageSize()).isEqualTo(4 * 1024 * 1024); + assertThat(rocketMQTemplate.getProducer().getRetryTimesWhenSendAsyncFailed()).isEqualTo(3); + assertThat(rocketMQTemplate.getProducer().getRetryTimesWhenSendFailed()).isEqualTo(3); + assertThat(rocketMQTemplate.getProducer().getCompressMsgBodyOverHowmuch()).isEqualTo(1024 * 4); + + assertThat(rocketMQTemplate.getConsumer().getNamesrvAddr()).isEqualTo("127.0.0.1:9876"); + assertThat(rocketMQTemplate.getConsumer().getConsumerGroup()).isEqualTo("spring_rocketmq"); + assertThat(rocketMQTemplate.getConsumer().getAccessChannel()).isEqualTo(AccessChannel.LOCAL); + } + + @Test + public void testTransactionListener() { + assertThat(((TransactionMQProducer) rocketMQTemplate.getProducer()).getTransactionListener()).isNotNull(); + } +} + +@RocketMQTransactionListener +class TransactionListenerImpl implements RocketMQLocalTransactionListener { + @Override + public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) { + return RocketMQLocalTransactionState.UNKNOWN; + } + + @Override + public RocketMQLocalTransactionState checkLocalTransaction(Message msg) { + return RocketMQLocalTransactionState.COMMIT; + } +} diff --git a/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/support/DefaultRocketMQListenerContainerTest.java b/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/support/DefaultRocketMQListenerContainerTest.java index ea85b98d..7133b982 100644 --- a/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/support/DefaultRocketMQListenerContainerTest.java +++ b/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/support/DefaultRocketMQListenerContainerTest.java @@ -16,11 +16,24 @@ */ package org.apache.rocketmq.spring.support; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.remoting.protocol.RemotingSerializable; import org.apache.rocketmq.spring.core.RocketMQListener; +import org.apache.rocketmq.spring.core.RocketMQReplyListener; import org.junit.Test; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import org.springframework.core.MethodParameter; +import org.springframework.messaging.Message; +import org.springframework.messaging.converter.CompositeMessageConverter; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.converter.StringMessageConverter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import org.springframework.messaging.support.MessageBuilder; import static org.assertj.core.api.Assertions.assertThat; @@ -36,7 +49,7 @@ public void testGetMessageType() throws Exception { public void onMessage(String message) { } }); - Class result = (Class)getMessageType.invoke(listenerContainer); + Class result = (Class) getMessageType.invoke(listenerContainer); assertThat(result.getName().equals(String.class.getName())); listenerContainer.setRocketMQListener(new RocketMQListener() { @@ -44,8 +57,123 @@ public void onMessage(String message) { public void onMessage(MessageExt message) { } }); - result = (Class)getMessageType.invoke(listenerContainer); + result = (Class) getMessageType.invoke(listenerContainer); + assertThat(result.getName().equals(MessageExt.class.getName())); + + listenerContainer.setRocketMQReplyListener(new RocketMQReplyListener() { + @Override + public String onMessage(MessageExt message) { + return "test"; + } + }); + result = (Class) getMessageType.invoke(listenerContainer); assertThat(result.getName().equals(MessageExt.class.getName())); + + listenerContainer.setRocketMQReplyListener(new RocketMQReplyListener() { + @Override + public String onMessage(String message) { + return "test"; + } + }); + result = (Class) getMessageType.invoke(listenerContainer); + assertThat(result.getName().equals(String.class.getName())); + } + + @Test + public void testDoConvertMessage() throws Exception { + DefaultRocketMQListenerContainer listenerContainer = new DefaultRocketMQListenerContainer(); + Method doConvertMessage = DefaultRocketMQListenerContainer.class.getDeclaredMethod("doConvertMessage", MessageExt.class); + doConvertMessage.setAccessible(true); + + listenerContainer.setRocketMQListener(new RocketMQListener() { + @Override + public void onMessage(String message) { + } + }); + + Field messageType = DefaultRocketMQListenerContainer.class.getDeclaredField("messageType"); + messageType.setAccessible(true); + messageType.set(listenerContainer, String.class); + MessageExt messageExt = new MessageExt(0, System.currentTimeMillis(), null, System.currentTimeMillis(), null, null); + messageExt.setBody("hello".getBytes()); + String result = (String) doConvertMessage.invoke(listenerContainer, messageExt); + assertThat(result).isEqualTo("hello"); + + listenerContainer.setRocketMQListener(new RocketMQListener() { + @Override + public void onMessage(MessageExt message) { + } + }); + Field messageType2 = DefaultRocketMQListenerContainer.class.getDeclaredField("messageType"); + messageType2.setAccessible(true); + messageType2.set(listenerContainer, MessageExt.class); + messageExt = new MessageExt(0, System.currentTimeMillis(), null, System.currentTimeMillis(), null, null); + messageExt.setBody("hello".getBytes()); + MessageExt result2 = (MessageExt) doConvertMessage.invoke(listenerContainer, messageExt); + assertThat(result2).isEqualTo(messageExt); + + listenerContainer.setRocketMQListener(new RocketMQListener() { + @Override + public void onMessage(User message) { + } + }); + } + + @Test + public void testGenericMessageType() throws Exception { + DefaultRocketMQListenerContainer listenerContainer = new DefaultRocketMQListenerContainer(); + listenerContainer.setMessageConverter(new CompositeMessageConverter(Arrays.asList(new StringMessageConverter(), new MappingJackson2MessageConverter()))); + + Method getMessageType = DefaultRocketMQListenerContainer.class.getDeclaredMethod("getMessageType"); + Method getMethodParameter = DefaultRocketMQListenerContainer.class.getDeclaredMethod("getMethodParameter"); + getMessageType.setAccessible(true); + getMethodParameter.setAccessible(true); + listenerContainer.setRocketMQListener(new RocketMQListener>() { + @Override + public void onMessage(ArrayList message) { + + } + }); + + ParameterizedType type = (ParameterizedType) getMessageType.invoke(listenerContainer); + assertThat(type.getRawType() == ArrayList.class); + MethodParameter methodParameter = ((MethodParameter) getMethodParameter.invoke(listenerContainer)); + assertThat(methodParameter.getParameterType() == ArrayList.class); + + listenerContainer.setRocketMQReplyListener(new RocketMQReplyListener, String>() { + @Override + public String onMessage(ArrayList message) { + return "test"; + } + }); + + type = (ParameterizedType) getMessageType.invoke(listenerContainer); + assertThat(type.getRawType() == ArrayList.class); + methodParameter = ((MethodParameter) getMethodParameter.invoke(listenerContainer)); + assertThat(methodParameter.getParameterType() == ArrayList.class); + } + + class User { + private String userName; + private int userAge; + + public String getUserName() { + return userName; + } + + public User setUserName(String userName) { + this.userName = userName; + return this; + } + + public int getUserAge() { + return userAge; + } + + public User setUserAge(int userAge) { + this.userAge = userAge; + return this; + } } } diff --git a/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/support/RocketMQUtilTest.java b/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/support/RocketMQUtilTest.java index 3704802d..e5584836 100644 --- a/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/support/RocketMQUtilTest.java +++ b/rocketmq-spring-boot/src/test/java/org/apache/rocketmq/spring/support/RocketMQUtilTest.java @@ -16,13 +16,17 @@ */ package org.apache.rocketmq.spring.support; -import java.util.Arrays; - import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Arrays; +import org.apache.rocketmq.acl.common.AclClientRPCHook; +import org.apache.rocketmq.acl.common.SessionCredentials; +import org.apache.rocketmq.common.UtilAll; +import org.apache.rocketmq.remoting.RPCHook; import org.junit.Test; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; +import static org.apache.rocketmq.spring.support.RocketMQUtil.toRocketHeaderKey; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -54,8 +58,8 @@ public void testPayload() { org.apache.rocketmq.common.message.Message rocketMsg2 = RocketMQUtil.convertToRocketMessage(objectMapper, charset, destination, msgWithBytePayload); - assertTrue(Arrays.equals(((String)msgWithStringPayload.getPayload()).getBytes(), rocketMsg1.getBody())); - assertTrue(Arrays.equals((byte[])msgWithBytePayload.getPayload(), rocketMsg2.getBody())); + assertTrue(Arrays.equals(((String) msgWithStringPayload.getPayload()).getBytes(), rocketMsg1.getBody())); + assertTrue(Arrays.equals((byte[]) msgWithBytePayload.getPayload(), rocketMsg2.getBody())); } @Test @@ -88,6 +92,49 @@ public void testHeaderConvertToSpringMsg() { assertEquals(String.valueOf("1"), rocketMsg.getProperty("test")); assertEquals(String.valueOf("tags"), rocketMsg.getProperty(RocketMQHeaders.PREFIX + RocketMQHeaders.TAGS)); assertNull(rocketMsg.getTags()); + + rmqMsg.putUserProperty(toRocketHeaderKey(RocketMQHeaders.TAGS), "tags2"); + springMsg = RocketMQUtil.convertToSpringMessage(rmqMsg); + assertEquals("tags", springMsg.getHeaders().get(RocketMQHeaders.PREFIX + RocketMQHeaders.TAGS)); } + @Test + public void testConvertToRocketMessageWithMessageConvert() { + Message msgWithStringPayload = MessageBuilder.withPayload("test body") + .setHeader("test", 1) + .setHeader(RocketMQHeaders.TAGS, "tags") + .setHeader(RocketMQHeaders.KEYS, "my_keys") + .build(); + RocketMQMessageConverter messageConverter = new RocketMQMessageConverter(); + org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(messageConverter.getMessageConverter(), + "UTF-8", "test-topic", msgWithStringPayload); + assertEquals("1", rocketMsg.getProperty("test")); + assertNull(rocketMsg.getProperty(RocketMQHeaders.TAGS)); + assertEquals("my_keys", rocketMsg.getProperty(RocketMQHeaders.KEYS)); + + Message msgWithBytesPayload = MessageBuilder.withPayload("123".getBytes()).build(); + org.apache.rocketmq.common.message.Message rocketMsgWithObj = RocketMQUtil.convertToRocketMessage(messageConverter.getMessageConverter(), + "UTF-8", "test-topic", msgWithBytesPayload); + assertEquals("123", new String(rocketMsgWithObj.getBody())); + } + + @Test + public void testConvertToSpringMessage() { + org.apache.rocketmq.common.message.MessageExt rocketMsg = new org.apache.rocketmq.common.message.MessageExt(); + rocketMsg.setTopic("test"); + rocketMsg.setBody("test".getBytes()); + rocketMsg.setTags("tagA"); + rocketMsg.setKeys("key1"); + Message message = RocketMQUtil.convertToSpringMessage(rocketMsg); + assertEquals("test", message.getHeaders().get(toRocketHeaderKey(RocketMQHeaders.TOPIC))); + assertEquals("tagA", message.getHeaders().get(toRocketHeaderKey(RocketMQHeaders.TAGS))); + assertEquals("key1", message.getHeaders().get(toRocketHeaderKey(RocketMQHeaders.KEYS))); + } + + @Test + public void testGetInstanceName() { + String nameServer = "127.0.0.1:9876"; + String expected = "127.0.0.1:9876@"; + assertEquals(expected + UtilAll.getPid(), RocketMQUtil.getInstanceName(nameServer)); + } } \ No newline at end of file