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