├── LICENSE ├── README.md ├── pom.xml └── src ├── main └── java │ └── org │ └── rocketmq │ └── starter │ ├── ConsumerOperator.java │ ├── RocketMQConsumerListener.java │ ├── annotation │ ├── EnableRocketMQ.java │ ├── RocketMQListener.java │ └── RocketMQMessage.java │ ├── configuration │ ├── RocketMQAutoConfiguration.java │ └── RocketMQProperties.java │ ├── core │ ├── DefaultRocketMQListenerContainerConstants.java │ ├── RocketMQConfig.java │ ├── RocketMQProducer.java │ ├── consumer │ │ ├── ConsumeStatus.java │ │ ├── MessageContext.java │ │ ├── MessageHandler.java │ │ ├── MessageListenerConcurrentlyImpl.java │ │ ├── MessageListenerOrderlyImpl.java │ │ ├── MethodInvoker.java │ │ ├── OperationResult.java │ │ ├── RocketMQConsumerConfig.java │ │ ├── RocketMQListenerMethodAdapter.java │ │ ├── RocketMQMessageListenerContainer.java │ │ ├── RocketMQPushConsumerFactory.java │ │ ├── SimpleListenerFactory.java │ │ └── SubscriptionGroup.java │ └── producer │ │ ├── MessageProxy.java │ │ ├── NamedMessageQueueSelector.java │ │ ├── RocketMQProducerConfig.java │ │ ├── RocketMQProducerContainer.java │ │ └── RocketMQProducerTemplate.java │ ├── exception │ ├── ConsumeException.java │ ├── ContatinerInitException.java │ ├── MethodNotSupportException.java │ └── NoListenerFoundException.java │ ├── extension │ ├── InitBean.java │ ├── InitBeanAware.java │ ├── InterceptorInitBean.java │ ├── InterceptorInitBeanAware.java │ └── core │ │ ├── InitBeanFactory.java │ │ └── InterceptorInitBeanSupport.java │ ├── operation │ └── ContainerOperatorController.java │ └── suport │ └── MessageConverter.java └── test └── java └── org └── rocketmq └── spring └── starter └── RocketMQAutoConfigurationTests.java /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "{}" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright 2018 wanzhichao 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rocketmq-spring-boot-starter 2 | 3 | [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 4 | 5 | 6 | ## Quick Start 7 | 8 | ```xml 9 | 10 | 11 |    org.rocketmq.spring.boot 12 | rocketmq-spring-boot-starter 13 | 1.0.0.RELEASE 14 | 15 | ``` 16 | 17 | ### Consume Message 18 | 19 | 20 | ```properties 21 | ## application.properties 22 | spring.rocketmq.name-server=127.0.0.1:9876 23 | ``` 24 | 25 | > More relevant configurations for produce: 26 | > 27 | > ```properties 28 | > spring.rocketmq.producer.retry-times-when-send-async-failed=0 29 | > spring.rocketmq.producer.send-msg-timeout=300000 30 | > spring.rocketmq.producer.compress-msg-body-over-howmuch=4096 31 | > spring.rocketmq.producer.max-message-size=4194304 32 | > spring.rocketmq.producer.retry-another-broker-when-not-store-ok=false 33 | > spring.rocketmq.producer.retry-times-when-send-failed=2 34 | 35 | 36 | > Note: 37 | 38 | > Maybe you need change 127.0.0.1:9876 with your real NameServer address for RocketMQ 39 | 40 | ```java 41 | @SpringBootApplication 42 | @EnableRocketMQ 43 | public class RocketMQApplication { 44 | public static void main(String[] args) { 45 | SpringApplication.run(RocketMQApplication .class,args); 46 | } 47 | } 48 | 49 | 50 | @Slf4j 51 | @Service 52 | @RocketMQListener(topic = "topic-1") 53 | public class MyConsumer1 { 54 | @RocketMQMessage(messageClass = String.class,tag = "tag-1") 55 | public void onMessage(String message) { 56 | log.info("received message: {}", message); 57 | } 58 | } 59 | 60 | ``` 61 | 62 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 20 | 4.0.0 21 | 22 | org.rocketmq.spring.boot 23 | rocketmq-spring-boot-starter 24 | 1.0.0.RELEASE 25 | jar 26 | 27 | rocketmq-spring-boot-starter 28 | starter for rocketmq 29 | 30 | 31 | org.sonatype.oss 32 | oss-parent 33 | 7 34 | 35 | 36 | 37 | 38 | The Apache Software License, Version 2.0 39 | http://www.apache.org/licenses/LICENSE-2.0.txt 40 | repo 41 | 42 | 43 | 44 | 45 | 46 | He Jialin 47 | hejialin@rocketmq.org 48 | https://www.rocketmq.org/ 49 | 50 | 51 | 52 | 53 | UTF-8 54 | UTF-8 55 | 1.8 56 | 1.5.9.RELEASE 57 | 4.3.13.RELEASE 58 | 4.2.0 59 | 60 | 61 | 62 | 63 | org.apache.rocketmq 64 | rocketmq-client 65 | ${version.rocketmq} 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter 70 | true 71 | 72 | 73 | org.springframework.boot 74 | spring-boot-configuration-processor 75 | true 76 | 77 | 78 | org.projectlombok 79 | lombok 80 | provided 81 | 82 | 83 | junit 84 | junit 85 | test 86 | 87 | 88 | org.springframework 89 | spring-web 90 | ${version.spring} 91 | provided 92 | 93 | 94 | 95 | 96 | 97 | 98 | org.springframework.boot 99 | spring-boot-dependencies 100 | ${version.springboot} 101 | pom 102 | import 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | org.apache.maven.plugins 111 | maven-compiler-plugin 112 | 3.3 113 | 114 | ${project.build.sourceEncoding} 115 | ${version.java} 116 | ${version.java} 117 | true 118 | 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-source-plugin 123 | 2.4 124 | 125 | true 126 | 127 | 128 | 129 | compile 130 | 131 | jar 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/ConsumerOperator.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter; 2 | 3 | 4 | import org.rocketmq.starter.core.consumer.OperationResult; 5 | 6 | /** 7 | * 8 | * 消费者容器操作接口 9 | * 10 | * @author He Jialin 11 | */ 12 | public interface ConsumerOperator { 13 | 14 | 15 | /** 16 | * 根据topic暂停某个消费者的消费 17 | * @param topic 消费者的topic 18 | * @return 操作结果 19 | */ 20 | OperationResult suspendConsumer(String topic); 21 | 22 | /** 23 | * 根据topic恢复某个消费者的消费 24 | * @param topic 消费者topic 25 | * @return 操作结果 26 | */ 27 | OperationResult resumeConsumer(String topic); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/RocketMQConsumerListener.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter; 2 | 3 | 4 | import org.rocketmq.starter.exception.ConsumeException; 5 | import org.rocketmq.starter.core.consumer.RocketMQConsumerConfig; 6 | import org.rocketmq.starter.core.consumer.MessageContext; 7 | 8 | /** 9 | * @author He Jialin 10 | */ 11 | public interface RocketMQConsumerListener { 12 | 13 | void onMessage(E message, MessageContext messageContext) throws ConsumeException; 14 | 15 | RocketMQConsumerConfig getConsumerConfig(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/annotation/EnableRocketMQ.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.annotation; 2 | 3 | import org.rocketmq.starter.configuration.RocketMQAutoConfiguration; 4 | import org.springframework.context.annotation.Import; 5 | 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * Annotation to enable a RocketMQ implementation. 14 | * 15 | * @author He Jialin 16 | */ 17 | @Target(ElementType.TYPE) 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @Documented 20 | @Import(RocketMQAutoConfiguration.class) 21 | public @interface EnableRocketMQ { 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/annotation/RocketMQListener.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.annotation; 2 | 3 | 4 | import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; 5 | import org.springframework.stereotype.Component; 6 | import java.lang.annotation.Documented; 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | /** 13 | * DefaultMQPushConsumer 14 | * 15 | * @author He Jialin 16 | */ 17 | @Target(ElementType.TYPE) 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @Documented 20 | @Component 21 | public @interface RocketMQListener { 22 | 23 | /** 24 | * 是否为顺序消息 25 | * @return true:sequence message 26 | * false:non-sequential messages 27 | */ 28 | boolean orderly() default false; 29 | 30 | /** 31 | * 转换为DefaultMqPushConsumer后订阅的topic 32 | * @return DEFAULT_TOPIC 33 | */ 34 | String topic() default "DEFAULT_TOPIC"; 35 | 36 | /** 37 | * 消息模式,默认为集群模式 38 | * @see MessageModel 39 | * @return MessageModel.CLUSTERING 40 | */ 41 | MessageModel messageModel() default MessageModel.CLUSTERING; 42 | 43 | /** 44 | * 消费者组 45 | * @return DEFAULT_GROUP 46 | */ 47 | String consumerGroup() default "DEFAULT_GROUP"; 48 | 49 | /** 50 | * 此消费者在消费时的最大线程数,如果在此处设置则使用此处设置的值 51 | * 否则统一使用配置文件中的值 52 | * @return 0 53 | */ 54 | int consumeThreadMax() default 0; 55 | 56 | /** 57 | * 此消费者在消费时的最小线程数,如果在此处设置则使用此处设置的值 58 | * 否则统一使用配置文件中的值 59 | * @return 0 60 | */ 61 | int consumeThreadMin() default 0; 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/annotation/RocketMQMessage.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.annotation; 2 | 3 | 4 | import java.lang.annotation.Documented; 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * 改注解运用在消费端的方法上,用来处理同一topic中不同的tag类型的消息 12 | * 13 | * @author He Jialin 14 | */ 15 | @Target(ElementType.METHOD) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Documented 18 | public @interface RocketMQMessage { 19 | 20 | /** 21 | * 订阅的tag 22 | * @return "*" 23 | */ 24 | String tag() default "*"; 25 | 26 | /** 27 | * 请求方消息类型 28 | * @return Object.class 29 | */ 30 | Class messageClass() default Object.class; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/configuration/RocketMQAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.configuration; 2 | 3 | 4 | import org.apache.rocketmq.client.impl.MQClientAPIImpl; 5 | import org.apache.rocketmq.client.producer.DefaultMQProducer; 6 | import org.rocketmq.starter.core.consumer.RocketMQMessageListenerContainer; 7 | import org.rocketmq.starter.core.consumer.SimpleListenerFactory; 8 | import org.rocketmq.starter.core.producer.RocketMQProducerContainer; 9 | import org.rocketmq.starter.extension.core.InitBeanFactory; 10 | import org.rocketmq.starter.core.consumer.MethodInvoker; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 13 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 15 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | import org.springframework.core.annotation.Order; 19 | import org.springframework.util.Assert; 20 | 21 | /** 22 | * 自动化配置类 23 | * 24 | * @author He Jialin 25 | */ 26 | @Configuration 27 | @EnableConfigurationProperties(RocketMQProperties.class) 28 | @ConditionalOnClass(MQClientAPIImpl.class) 29 | @Order 30 | public class RocketMQAutoConfiguration { 31 | 32 | @Autowired 33 | private RocketMQProperties rocketMQProperties; 34 | 35 | @Bean 36 | @ConditionalOnClass(DefaultMQProducer.class) 37 | @ConditionalOnMissingBean(DefaultMQProducer.class) 38 | @ConditionalOnProperty(prefix = "spring.rocketmq", value = {"nameServer", "producer.group"}) 39 | public DefaultMQProducer mqProducer(RocketMQProperties rocketMQProperties) { 40 | 41 | RocketMQProperties.Producer producerConfig = rocketMQProperties.getProducer(); 42 | String groupName = producerConfig.getGroup(); 43 | Assert.hasText(groupName, "[spring.rocketmq.producer.group] must not be null"); 44 | 45 | DefaultMQProducer producer = new DefaultMQProducer(producerConfig.getGroup()); 46 | producer.setNamesrvAddr(rocketMQProperties.getNameServer()); 47 | producer.setSendMsgTimeout(producerConfig.getSendMsgTimeout()); 48 | producer.setRetryTimesWhenSendFailed(producerConfig.getRetryTimesWhenSendFailed()); 49 | producer.setRetryTimesWhenSendAsyncFailed(producerConfig.getRetryTimesWhenSendAsyncFailed()); 50 | producer.setMaxMessageSize(producerConfig.getMaxMessageSize()); 51 | producer.setCompressMsgBodyOverHowmuch(producerConfig.getCompressMsgBodyOverHowmuch()); 52 | producer.setRetryAnotherBrokerWhenNotStoreOK(producerConfig.isRetryAnotherBrokerWhenNotStoreOk()); 53 | 54 | return producer; 55 | } 56 | 57 | @Bean 58 | @ConditionalOnMissingBean(SimpleListenerFactory.class) 59 | public InitBeanFactory initBeanFactory() { 60 | return new InitBeanFactory(); 61 | } 62 | 63 | @Bean 64 | @ConditionalOnMissingBean(MethodInvoker.class) 65 | public MethodInvoker methodInvoker() { 66 | return new MethodInvoker(); 67 | } 68 | 69 | @Bean 70 | @ConditionalOnMissingBean(RocketMQProducerContainer.class) 71 | public RocketMQProducerContainer mqProducerContainer() { 72 | return new RocketMQProducerContainer(); 73 | } 74 | 75 | @Bean 76 | @ConditionalOnMissingBean(RocketMQMessageListenerContainer.class) 77 | public RocketMQMessageListenerContainer mqMessageListenerContainer() { 78 | return new RocketMQMessageListenerContainer(rocketMQProperties.getNameServer()); 79 | } 80 | 81 | 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/configuration/RocketMQProperties.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.configuration; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | 6 | /** 7 | * rocketmq基本配置 8 | * 9 | * @author He Jialin 10 | */ 11 | @SuppressWarnings("WeakerAccess") 12 | @ConfigurationProperties(prefix = "spring.rocketmq") 13 | @Data 14 | public final class RocketMQProperties { 15 | 16 | /** 17 | * @see org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#namesrvAddr 18 | */ 19 | private String nameServer = "127.0.0.1:9876"; 20 | 21 | /** 22 | * @see org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#consumeThreadMin 23 | */ 24 | private int consumeThreadMin = 20; 25 | 26 | /** 27 | * @see org.apache.rocketmq.client.consumer.DefaultMQPushConsumer#consumeThreadMax 28 | */ 29 | private int consumeThreadMax = 64; 30 | 31 | private Producer producer; 32 | 33 | @Data 34 | public static class Producer { 35 | 36 | /** 37 | * name of producer 38 | */ 39 | private String group; 40 | 41 | /** 42 | * millis of send message timeout 43 | */ 44 | private int sendMsgTimeout = 3000; 45 | 46 | /** 47 | * Compress message body threshold, namely, message body larger than 4k will be compressed on default. 48 | */ 49 | private int compressMsgBodyOverHowmuch = 1024 * 4; 50 | 51 | /** 52 | *

Maximum number of retry to perform internally before claiming sending failure in synchronous mode.

53 | * This may potentially cause message duplication which is up to application developers to resolve. 54 | */ 55 | private int retryTimesWhenSendFailed = 2; 56 | 57 | /** 58 | *

Maximum number of retry to perform internally before claiming sending failure in asynchronous mode.

59 | * This may potentially cause message duplication which is up to application developers to resolve. 60 | */ 61 | private int retryTimesWhenSendAsyncFailed = 2; 62 | 63 | /** 64 | * Indicate whether to retry another broker on sending failure internally. 65 | */ 66 | private boolean retryAnotherBrokerWhenNotStoreOk = false; 67 | 68 | /** 69 | * Maximum allowed message size in bytes. // 4M 70 | */ 71 | private int maxMessageSize = 1024 * 1024 * 4; 72 | 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/DefaultRocketMQListenerContainerConstants.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core; 2 | 3 | /** 4 | *

5 | * Note: 6 | *

7 | * Date: 04/08/2018 18:25. 8 | * 9 | * @author He Jialin 10 | */ 11 | public class DefaultRocketMQListenerContainerConstants { 12 | public static final String PROP_NAMESERVER = "nameServer"; 13 | public static final String PROP_TOPIC = "topic"; 14 | public static final String PROP_CONSUMER_GROUP = "consumerGroup"; 15 | public static final String PROP_CONSUME_MODE = "consumeMode"; 16 | public static final String PROP_CONSUME_THREAD_MAX = "consumeThreadMax"; 17 | public static final String PROP_MESSAGE_MODEL = "messageModel"; 18 | public static final String PROP_SELECTOR_EXPRESS = "selectorExpress"; 19 | public static final String PROP_SELECTOR_TYPE = "selectorType"; 20 | public static final String PROP_ROCKETMQ_LISTENER = "rocketMQListener"; 21 | public static final String PROP_OBJECT_MAPPER = "objectMapper"; 22 | public static final String METHOD_DESTROY = "destroy"; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/RocketMQConfig.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core; 2 | 3 | /** 4 | * @author He Jialin 5 | */ 6 | public class RocketMQConfig { 7 | private Class messageClass; 8 | private boolean orderlyMessage; 9 | 10 | public Class getMessageClass() { 11 | return messageClass; 12 | } 13 | 14 | public void setMessageClass(Class messageClass) { 15 | this.messageClass = messageClass; 16 | } 17 | 18 | public boolean isOrderlyMessage() { 19 | return orderlyMessage; 20 | } 21 | 22 | public void setOrderlyMessage(boolean orderlyMessage) { 23 | this.orderlyMessage = orderlyMessage; 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/RocketMQProducer.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core; 2 | 3 | 4 | import org.apache.rocketmq.client.exception.MQBrokerException; 5 | import org.apache.rocketmq.client.exception.MQClientException; 6 | import org.apache.rocketmq.client.producer.SendResult; 7 | import org.apache.rocketmq.common.message.Message; 8 | import org.apache.rocketmq.remoting.exception.RemotingException; 9 | 10 | import org.rocketmq.starter.core.producer.MessageProxy; 11 | 12 | 13 | /** 14 | * @author He Jialin 15 | */ 16 | public interface RocketMQProducer { 17 | 18 | SendResult send(Message message) throws InterruptedException, RemotingException, MQClientException, MQBrokerException; 19 | 20 | void send(MessageProxy messageProxy) throws MQClientException, InterruptedException, RemotingException; 21 | 22 | void start() throws MQClientException; 23 | 24 | void shutdown(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/ConsumeStatus.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | /** 4 | * 消费状态的枚举 5 | * 6 | * @author He Jialin 7 | */ 8 | public enum ConsumeStatus { 9 | /** 10 | * 消费成功 11 | */ 12 | SUCCESS, 13 | /** 14 | * 需要重试 15 | */ 16 | RETRY 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/MessageContext.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | 4 | import org.apache.rocketmq.common.message.MessageExt; 5 | import org.apache.rocketmq.common.message.MessageQueue; 6 | 7 | import lombok.Data; 8 | import lombok.ToString; 9 | 10 | /** 11 | * 消费时,当前所消费的消息的上下文信息 12 | * 13 | * @author He Jialin 14 | */ 15 | @ToString 16 | @Data 17 | public final class MessageContext { 18 | 19 | /** 20 | * 所消费消息所在的消息队列 21 | * 22 | * @see MessageQueue 23 | */ 24 | private MessageQueue messageQueue; 25 | 26 | /** 27 | * 所消费的消息的扩展属性 28 | * 29 | * @see MessageExt 30 | */ 31 | private MessageExt messageExt; 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/MessageHandler.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.JSONObject; 6 | import org.apache.rocketmq.common.message.MessageExt; 7 | import org.apache.rocketmq.common.message.MessageQueue; 8 | import org.rocketmq.starter.RocketMQConsumerListener; 9 | import org.rocketmq.starter.exception.ConsumeException; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.io.UnsupportedEncodingException; 13 | import java.util.List; 14 | 15 | /** 16 | * 消息处理器 17 | * 18 | * @author He Jialin 19 | */ 20 | @Slf4j 21 | public class MessageHandler { 22 | 23 | 24 | @SuppressWarnings("unchecked") 25 | public static ConsumeStatus handleMessage(final RocketMQConsumerListener listener, 26 | final List msgs, final MessageQueue messageQueue) { 27 | try { 28 | for (MessageExt msg : msgs) { 29 | byte[] body = msg.getBody(); 30 | final MessageContext messageContext = new MessageContext(); 31 | messageContext.setMessageExt(msg); 32 | messageContext.setMessageQueue(messageQueue); 33 | if (log.isDebugEnabled()) { 34 | log.debug("开始消费,msg={}", msg); 35 | } 36 | try { 37 | JSONObject jsonStr = JSONObject.parseObject(new String(body, "UTF-8")); 38 | listener.onMessage(JSON.parseObject(jsonStr.toJSONString(), 39 | listener.getConsumerConfig().getMessageClass()), messageContext); 40 | } catch (Exception e) { 41 | listener.onMessage(new String(body, "UTF-8"), messageContext); 42 | } 43 | if (log.isDebugEnabled()) { 44 | log.debug("消费完成"); 45 | } 46 | } 47 | } catch (Exception e) { 48 | return handleException(e); 49 | } 50 | return ConsumeStatus.SUCCESS; 51 | } 52 | 53 | /** 54 | * 异常处理 55 | * 56 | * @param e 捕获的异常 57 | * @return 消息消费结果 58 | */ 59 | private static ConsumeStatus handleException(final Exception e) { 60 | Class exceptionClass = e.getClass(); 61 | if (exceptionClass.equals(UnsupportedEncodingException.class)) { 62 | log.error(e.getMessage()); 63 | } else if (exceptionClass.equals(ConsumeException.class)) { 64 | log.error(e.getMessage()); 65 | } 66 | return ConsumeStatus.RETRY; 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/MessageListenerConcurrentlyImpl.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; 4 | import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; 5 | import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; 6 | import org.apache.rocketmq.common.message.MessageExt; 7 | 8 | import org.rocketmq.starter.RocketMQConsumerListener; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * 并发消费监听默认实现 14 | * 15 | * @author He Jialin 16 | */ 17 | public final class MessageListenerConcurrentlyImpl implements MessageListenerConcurrently { 18 | 19 | private final RocketMQConsumerListener listener; 20 | 21 | MessageListenerConcurrentlyImpl(RocketMQConsumerListener listener) { 22 | this.listener = listener; 23 | } 24 | 25 | /** 26 | * 消费消息 27 | * 28 | * @param msgs msgs.size() >= 1 29 | * DefaultMQPushConsumer.consumeMessageBatchMaxSize=1,you can modify here 30 | * 这里只设置为1,当设置为多个时,msgs中只要有一条消息消费失败,就会整体重试 31 | * @param context 上下文信息 32 | * @return 消费状态 成功(CONSUME_SUCCESS)或者 重试 (RECONSUME_LATER) 33 | */ 34 | @Override 35 | public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { 36 | ConsumeStatus status = MessageHandler.handleMessage(listener, msgs, context.getMessageQueue()); 37 | if (status.equals(ConsumeStatus.SUCCESS)) { 38 | return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; 39 | } else { 40 | return ConsumeConcurrentlyStatus.RECONSUME_LATER; 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/MessageListenerOrderlyImpl.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; 4 | import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; 5 | import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; 6 | import org.apache.rocketmq.common.message.MessageExt; 7 | 8 | import org.rocketmq.starter.RocketMQConsumerListener; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * 顺序消费默认监听实现 14 | * 15 | * @author He Jialin 16 | */ 17 | public final class MessageListenerOrderlyImpl implements MessageListenerOrderly { 18 | 19 | private final RocketMQConsumerListener listener; 20 | 21 | MessageListenerOrderlyImpl(RocketMQConsumerListener listener) { 22 | this.listener = listener; 23 | } 24 | 25 | /** 26 | * @param msgs 每次只取一条消息 27 | * @param context 封装队列和消息信息 28 | * @return 消费状态 成功(SUCCESS) 重试(SUSPEND_CURRENT_QUEUE_A_MOMENT) 29 | */ 30 | @Override 31 | public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { 32 | ConsumeStatus status = MessageHandler.handleMessage(listener, msgs, context.getMessageQueue()); 33 | if (status.equals(ConsumeStatus.SUCCESS)) { 34 | return ConsumeOrderlyStatus.SUCCESS; 35 | } else { 36 | return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/MethodInvoker.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | 4 | import org.rocketmq.starter.exception.ConsumeException; 5 | import org.rocketmq.starter.extension.InterceptorInitBean; 6 | import org.rocketmq.starter.extension.InterceptorInitBeanAware; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.util.ReflectionUtils; 10 | 11 | import java.lang.reflect.Method; 12 | import java.util.Arrays; 13 | import java.util.Collection; 14 | 15 | /** 16 | * 方法执行器,并实现InterceptorHookAware接口,通过set注入hook,实现方法执行前后的动态扩展 17 | * 该类注册到Spring容器进行管理 18 | * 19 | * @author He Jialin 20 | */ 21 | public class MethodInvoker implements InterceptorInitBeanAware { 22 | 23 | private static final Logger logger = LoggerFactory.getLogger(MethodInvoker.class); 24 | 25 | private InterceptorInitBean hook; 26 | 27 | /** 28 | * 对目标方法进行调用 29 | * 30 | * @param delegate 方法所在对象 31 | * @param method 对应方法 32 | * @param args 方法参数 33 | */ 34 | void invoke(Object delegate, final Method method, Object... args) { 35 | try { 36 | if(!hook.preHandle(args)){ 37 | return; 38 | } 39 | } catch (Exception e) { 40 | handleHookException(e); 41 | } 42 | 43 | Class[] parmTypes = method.getParameterTypes(); 44 | //检查方法中是否有MessageContext参数 45 | boolean hasContext = Arrays.stream(parmTypes).anyMatch(parmClazz -> parmClazz.equals(MessageContext.class)); 46 | try { 47 | if (hasContext) { 48 | ReflectionUtils.invokeMethod(method, delegate, args); 49 | } else { 50 | ReflectionUtils.invokeMethod(method, delegate, args[0]); 51 | } 52 | }catch (Exception e){ 53 | hook.nextHandle(false,args); 54 | throw new ConsumeException(e); 55 | } 56 | try { 57 | hook.nextHandle(true,args); 58 | } catch (Exception e) { 59 | handleHookException(e); 60 | } 61 | 62 | } 63 | 64 | /** 65 | * 对多个目标方法进行调用,调用策略为循环按顺序调用 66 | * 67 | * @param delegate 目标类 68 | * @param methods 目标方法 69 | * @param args 参数 70 | */ 71 | void invoke(Object delegate, Collection methods, Object... args) { 72 | methods.forEach(method -> invoke(delegate, method, args)); 73 | } 74 | 75 | 76 | 77 | @Override 78 | public void setHook(InterceptorInitBean hook) { 79 | this.hook = hook; 80 | } 81 | 82 | private void handleHookException(Exception e) { 83 | logger.error(e.getMessage(),e); 84 | } 85 | } 86 | 87 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/OperationResult.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 7 | * @author He Jialin 8 | */ 9 | 10 | @Data 11 | public class OperationResult { 12 | 13 | private Boolean success; 14 | 15 | private String message; 16 | 17 | private T data; 18 | 19 | public static OperationResult result(Boolean success,String message){ 20 | OperationResult result = new OperationResult(); 21 | result.setMessage(message); 22 | result.setSuccess(success); 23 | return result; 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/RocketMQConsumerConfig.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | 4 | import org.apache.rocketmq.common.protocol.heartbeat.MessageModel; 5 | 6 | import org.rocketmq.starter.core.RocketMQConfig; 7 | import lombok.Data; 8 | import lombok.EqualsAndHashCode; 9 | 10 | import java.util.Map; 11 | 12 | /** 13 | * consumer配置类,用于封装consumer的基本配置 14 | * 15 | * @author He Jialin 16 | */ 17 | @Data 18 | @EqualsAndHashCode(callSuper = false) 19 | public final class RocketMQConsumerConfig extends RocketMQConfig { 20 | 21 | /** 22 | * 消费者topic 23 | */ 24 | private String topic; 25 | 26 | /** 27 | * 消费者组 28 | */ 29 | private String consumerGroup; 30 | 31 | /** 32 | * 消息模式 33 | * 34 | * @see MessageModel 35 | */ 36 | private MessageModel messageModel; 37 | 38 | /** 39 | * 保存一个消费者订阅的topic下不同的tag以及tag对应的消息体类型 40 | */ 41 | private Map> tags; 42 | 43 | /** 44 | * 一个消费者默认最小线程数 45 | */ 46 | private int consumeThreadMin; 47 | /** 48 | * 一个消费者默认最大线程数 49 | */ 50 | private int consumeThreadMax; 51 | 52 | 53 | public static ConsumerConfigBuilder builder() { 54 | return new ConsumerConfigBuilder(); 55 | } 56 | 57 | /** 58 | * ConsumerConfig的建造者,方便构建不同的配置 59 | */ 60 | public static class ConsumerConfigBuilder { 61 | 62 | private final RocketMQConsumerConfig consumerConfig = new RocketMQConsumerConfig(); 63 | 64 | 65 | public ConsumerConfigBuilder messageClass(Class messageClass) { 66 | this.consumerConfig.setMessageClass(messageClass); 67 | return this; 68 | } 69 | 70 | public ConsumerConfigBuilder topic(String topic) { 71 | this.consumerConfig.setTopic(topic); 72 | return this; 73 | } 74 | 75 | public ConsumerConfigBuilder orderlyMessage(boolean orderlyMessage) { 76 | this.consumerConfig.setOrderlyMessage(orderlyMessage); 77 | return this; 78 | } 79 | 80 | public ConsumerConfigBuilder consumerGroup(String consumerGroup) { 81 | this.consumerConfig.setConsumerGroup(consumerGroup); 82 | return this; 83 | } 84 | 85 | public ConsumerConfigBuilder messageModel(MessageModel messageModel) { 86 | this.consumerConfig.setMessageModel(messageModel); 87 | return this; 88 | } 89 | 90 | public ConsumerConfigBuilder consumeThreadMax(int consumeThreadMax) { 91 | this.consumerConfig.setConsumeThreadMax(consumeThreadMax); 92 | return this; 93 | } 94 | 95 | public ConsumerConfigBuilder consumeThreadMin(int consumeThreadMin) { 96 | this.consumerConfig.setConsumeThreadMin(consumeThreadMin); 97 | return this; 98 | } 99 | 100 | public RocketMQConsumerConfig build() { 101 | return this.consumerConfig; 102 | } 103 | 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/RocketMQListenerMethodAdapter.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | 4 | import org.rocketmq.starter.RocketMQConsumerListener; 5 | import org.rocketmq.starter.annotation.RocketMQListener; 6 | import org.rocketmq.starter.annotation.RocketMQMessage; 7 | import org.rocketmq.starter.exception.ConsumeException; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.lang.reflect.Method; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * @author He Jialin 17 | */ 18 | public final class RocketMQListenerMethodAdapter implements RocketMQConsumerListener { 19 | 20 | private static final Logger logger = LoggerFactory.getLogger(RocketMQListenerMethodAdapter.class); 21 | 22 | private final SubscriptionGroup subscriptionGroup; 23 | 24 | private RocketMQConsumerConfig consumerConfig; 25 | 26 | private MethodInvoker invoker; 27 | 28 | RocketMQListenerMethodAdapter(SubscriptionGroup subscriptionGroup) { 29 | this.subscriptionGroup = subscriptionGroup; 30 | initConfig(subscriptionGroup); 31 | } 32 | 33 | private void initConfig(SubscriptionGroup subscriptionGroup) { 34 | RocketMQListener rocketMQListener = subscriptionGroup.getTarget().getClass().getAnnotation(RocketMQListener.class); 35 | consumerConfig = RocketMQConsumerConfig.builder() 36 | .consumerGroup(rocketMQListener.consumerGroup()) 37 | .messageModel(rocketMQListener.messageModel()) 38 | .orderlyMessage(rocketMQListener.orderly()) 39 | .topic(rocketMQListener.topic()) 40 | .consumeThreadMax(rocketMQListener.consumeThreadMax()) 41 | .consumeThreadMin(rocketMQListener.consumeThreadMin()) 42 | .build(); 43 | Map> tags = new HashMap<>(); 44 | subscriptionGroup.getTagList().forEach(tag -> { 45 | RocketMQMessage rocketMQMessage = subscriptionGroup.getMethod(tag).getAnnotation(RocketMQMessage.class); 46 | tags.put(tag, rocketMQMessage.messageClass()); 47 | consumerConfig.setMessageClass(rocketMQMessage.messageClass()); 48 | }); 49 | consumerConfig.setTags(tags); 50 | } 51 | 52 | 53 | @Override 54 | public void onMessage(E message, MessageContext context) throws ConsumeException { 55 | if (logger.isDebugEnabled()) { 56 | logger.debug("received message:{}", message); 57 | } 58 | String tag = context.getMessageExt().getTags(); 59 | Method method = this.subscriptionGroup.getMethod(tag); 60 | Object delegate = this.subscriptionGroup.getTarget(); 61 | if (method != null) { 62 | try { 63 | invoker.invoke(delegate, method, message, context); 64 | } catch (Exception e) { 65 | throw new ConsumeException(e); 66 | } 67 | } else { 68 | if (("*").equals(tag.trim())) { 69 | invoker.invoke(delegate, this.subscriptionGroup.getAllMethods(), message, context); 70 | } else { 71 | throw new ConsumeException("No way to find the corresponding tag"); 72 | } 73 | } 74 | 75 | 76 | } 77 | 78 | @Override 79 | public RocketMQConsumerConfig getConsumerConfig() { 80 | return this.consumerConfig; 81 | } 82 | 83 | 84 | public void setInvoker(MethodInvoker invoker) { 85 | this.invoker = invoker; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/RocketMQMessageListenerContainer.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; 4 | import org.apache.rocketmq.client.exception.MQClientException; 5 | 6 | import org.rocketmq.starter.ConsumerOperator; 7 | import org.rocketmq.starter.RocketMQConsumerListener; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.BeansException; 11 | import org.springframework.beans.factory.BeanNameAware; 12 | import org.springframework.beans.factory.DisposableBean; 13 | import org.springframework.beans.factory.InitializingBean; 14 | import org.springframework.context.ApplicationContext; 15 | import org.springframework.context.ApplicationContextAware; 16 | import org.springframework.context.SmartLifecycle; 17 | 18 | import java.util.AbstractMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.concurrent.ConcurrentHashMap; 22 | import java.util.concurrent.CopyOnWriteArrayList; 23 | 24 | /** 25 | * @author He Jialin 26 | */ 27 | public class RocketMQMessageListenerContainer implements InitializingBean, DisposableBean, BeanNameAware, 28 | SmartLifecycle, ApplicationContextAware, ConsumerOperator { 29 | 30 | private final Logger logger = LoggerFactory.getLogger(getClass()); 31 | 32 | private String nameSrvAddr; 33 | 34 | /** 35 | * Minimum consumer thread number 36 | */ 37 | private int consumeThreadMin = 20; 38 | 39 | /** 40 | * Max consumer thread number 41 | */ 42 | private int consumeThreadMax = 64; 43 | 44 | 45 | private final Object monitor = new Object(); 46 | 47 | private final Object mapMonitor = new Object(); 48 | 49 | private volatile boolean running = false; 50 | 51 | private volatile boolean initialized = false; 52 | 53 | private List pushConsumers = new CopyOnWriteArrayList<>(); 54 | 55 | private Map pushConsumerMap = new ConcurrentHashMap<>(); 56 | 57 | private Map removedMap = new ConcurrentHashMap<>(); 58 | 59 | private Map runningMap = new ConcurrentHashMap<>(); 60 | 61 | private Map> startErrMap = new ConcurrentHashMap<>(); 62 | 63 | private RocketMQPushConsumerFactory consumerFactory; 64 | 65 | private ApplicationContext applicationContext; 66 | 67 | private String beanName; 68 | 69 | public RocketMQMessageListenerContainer() { 70 | } 71 | 72 | public RocketMQMessageListenerContainer(String nameSrvAddr) { 73 | this.nameSrvAddr = nameSrvAddr; 74 | } 75 | 76 | public RocketMQMessageListenerContainer(String nameSrvAddr, int consumeThreadMin, int consumeThreadMax) { 77 | this.nameSrvAddr = nameSrvAddr; 78 | this.consumeThreadMin = consumeThreadMin; 79 | this.consumeThreadMax = consumeThreadMax; 80 | } 81 | 82 | @Override 83 | public void start() { 84 | if (!isRunning()) { 85 | running = true; 86 | synchronized (monitor) { 87 | registMessageListener(); 88 | startAllListener(); 89 | } 90 | } 91 | } 92 | 93 | private void startAllListener() { 94 | pushConsumerMap.forEach((topic, consumer) -> { 95 | try { 96 | consumer.start(); 97 | runningMap.put(topic, consumer); 98 | } catch (MQClientException e) { 99 | logger.error(e.getErrorMessage()); 100 | Map.Entry errEntry = 101 | new AbstractMap.SimpleEntry<>(consumer, e.getErrorMessage()); 102 | startErrMap.put(topic, errEntry); 103 | } 104 | }); 105 | } 106 | 107 | @Override 108 | public void stop() { 109 | if (isRunning()) { 110 | running = false; 111 | pushConsumers.forEach(DefaultMQPushConsumer::shutdown); 112 | } 113 | if (logger.isDebugEnabled()) { 114 | logger.debug("Stopped RocketMessageListenerContainer"); 115 | } 116 | } 117 | 118 | @Override 119 | public boolean isRunning() { 120 | return running; 121 | } 122 | 123 | 124 | @Override 125 | public void setBeanName(String name) { 126 | this.beanName = name; 127 | } 128 | 129 | @Override 130 | public void destroy() { 131 | this.initialized = false; 132 | stop(); 133 | } 134 | 135 | @Override 136 | public void afterPropertiesSet() { 137 | initMqPushConsumerFactory(); 138 | this.initialized = true; 139 | 140 | } 141 | 142 | private void initMqPushConsumerFactory() { 143 | this.consumerFactory = new RocketMQPushConsumerFactory(this.nameSrvAddr); 144 | this.consumerFactory.setApplicationContext(applicationContext); 145 | this.consumerFactory.setConsumeThreadMax(this.consumeThreadMax); 146 | this.consumerFactory.setConsumeThreadMin(this.consumeThreadMin); 147 | this.consumerFactory.afterPropertiesSet(); 148 | 149 | } 150 | 151 | @Override 152 | public boolean isAutoStartup() { 153 | return true; 154 | } 155 | 156 | @Override 157 | public void stop(Runnable callback) { 158 | stop(); 159 | callback.run(); 160 | } 161 | 162 | @Override 163 | public int getPhase() { 164 | return Integer.MAX_VALUE; 165 | } 166 | 167 | private void registMessageListener() { 168 | SimpleListenerFactory listenerFactory = consumerFactory.getListenerFactory(); 169 | pushConsumers.addAll(consumerFactory.getAllMQPushConsumer()); 170 | pushConsumerMap.putAll(consumerFactory.getPushConsumerMap()); 171 | Map listenerMap = listenerFactory.getAllListeners(); 172 | pushConsumerMap.forEach((topic, consumer) -> { 173 | RocketMQConsumerListener listener = listenerMap.get(topic); 174 | if (listener.getConsumerConfig().isOrderlyMessage()) { 175 | consumer.registerMessageListener(new MessageListenerOrderlyImpl(listener)); 176 | } else { 177 | consumer.registerMessageListener(new MessageListenerConcurrentlyImpl(listener)); 178 | } 179 | }); 180 | } 181 | 182 | 183 | @Override 184 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 185 | this.applicationContext = applicationContext; 186 | } 187 | 188 | @Override 189 | public OperationResult suspendConsumer(String topic) { 190 | return analyzeResult(topic, OperatinType.SUSPEND, () -> { 191 | DefaultMQPushConsumer consumer = runningMap.get(topic); 192 | consumer.suspend(); 193 | runningMap.remove(topic); 194 | removedMap.put(topic, consumer); 195 | }); 196 | } 197 | 198 | @Override 199 | public OperationResult resumeConsumer(String topic) { 200 | return analyzeResult(topic, OperatinType.RESUME, () -> { 201 | DefaultMQPushConsumer consumer = removedMap.get(topic); 202 | consumer.resume(); 203 | removedMap.remove(topic); 204 | runningMap.put(topic, consumer); 205 | }); 206 | } 207 | 208 | private OperationResult analyzeResult(String topic, OperatinType operatinType, Runnable runnable) { 209 | 210 | if (initialized) { 211 | FindResult findResult = findInMap(topic); 212 | OperationResult result = new OperationResult(); 213 | switch (findResult) { 214 | case NONE: 215 | result.setSuccess(false); 216 | result.setMessage("未找到对应的消费者"); 217 | break; 218 | case ERROR: 219 | result.setSuccess(false); 220 | result.setMessage("该消费者启动异常"); 221 | break; 222 | case RUNNING: 223 | if (operatinType.equals(OperatinType.SUSPEND)) { 224 | runnable.run(); 225 | result.setSuccess(true); 226 | result.setMessage("暂停成功"); 227 | } else { 228 | result.setMessage("该消费者正在运行中"); 229 | result.setSuccess(false); 230 | } 231 | break; 232 | case SUSPEND: 233 | if (operatinType.equals(OperatinType.RESUME)) { 234 | synchronized (mapMonitor) { 235 | runnable.run(); 236 | result.setSuccess(true); 237 | result.setMessage("运行成功"); 238 | } 239 | } else { 240 | result.setSuccess(false); 241 | result.setMessage("该消费者正在暂停中"); 242 | } 243 | break; 244 | default: 245 | result.setMessage("未知异常"); 246 | result.setSuccess(false); 247 | break; 248 | } 249 | return result; 250 | 251 | } 252 | return OperationResult.result(false, "容器未初始化"); 253 | } 254 | 255 | 256 | private FindResult findInMap(String topic) { 257 | if (!pushConsumerMap.containsKey(topic)) { 258 | return FindResult.NONE; 259 | } else { 260 | if (startErrMap.containsKey(topic)) { 261 | return FindResult.START_ERROR; 262 | } 263 | if (runningMap.containsKey(topic)) { 264 | return FindResult.RUNNING; 265 | } 266 | if (removedMap.containsKey(topic)) { 267 | return FindResult.SUSPEND; 268 | } 269 | } 270 | return FindResult.ERROR; 271 | } 272 | 273 | private enum FindResult { 274 | //未找到 275 | NONE, 276 | //启动异常 277 | START_ERROR, 278 | //运行中 279 | RUNNING, 280 | //停止 281 | SUSPEND, 282 | //其他异常 283 | ERROR 284 | } 285 | 286 | private enum OperatinType { 287 | //恢复 288 | RESUME, 289 | //暂停 290 | SUSPEND 291 | } 292 | 293 | public String getNameSrvAddr() { 294 | return nameSrvAddr; 295 | } 296 | 297 | public void setNameSrvAddr(String nameSrvAddr) { 298 | this.nameSrvAddr = nameSrvAddr; 299 | } 300 | 301 | public RocketMQPushConsumerFactory getConsumerFactory() { 302 | return consumerFactory; 303 | } 304 | 305 | public void setConsumerFactory(RocketMQPushConsumerFactory consumerFactory) { 306 | this.consumerFactory = consumerFactory; 307 | } 308 | 309 | public int getConsumeThreadMin() { 310 | return consumeThreadMin; 311 | } 312 | 313 | public void setConsumeThreadMin(int consumeThreadMin) { 314 | this.consumeThreadMin = consumeThreadMin; 315 | } 316 | 317 | public int getConsumeThreadMax() { 318 | return consumeThreadMax; 319 | } 320 | 321 | public void setConsumeThreadMax(int consumeThreadMax) { 322 | this.consumeThreadMax = consumeThreadMax; 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/RocketMQPushConsumerFactory.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | 4 | import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; 5 | import org.apache.rocketmq.client.exception.MQClientException; 6 | 7 | import org.rocketmq.starter.RocketMQConsumerListener; 8 | import org.springframework.beans.BeansException; 9 | import org.springframework.beans.factory.InitializingBean; 10 | import org.springframework.context.ApplicationContext; 11 | import org.springframework.context.ApplicationContextAware; 12 | 13 | import java.util.ArrayList; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * 用来生成DefaultMQPushConsumer 20 | * 21 | * @author He Jialin 22 | */ 23 | public class RocketMQPushConsumerFactory implements InitializingBean, ApplicationContextAware { 24 | 25 | private String nameSrvAddr; 26 | 27 | private SimpleListenerFactory listenerFactory; 28 | 29 | private ApplicationContext applicationContext; 30 | 31 | private Map pushConsumerMap; 32 | 33 | private List pushConsumers; 34 | 35 | private int consumeThreadMin; 36 | 37 | private int consumeThreadMax; 38 | 39 | 40 | RocketMQPushConsumerFactory(String nameSrvAddr) { 41 | this.nameSrvAddr = nameSrvAddr; 42 | } 43 | 44 | public Map getPushConsumerMap() { 45 | return pushConsumerMap; 46 | } 47 | 48 | 49 | private DefaultMQPushConsumer createDefaultMQPushConsumer(RocketMQConsumerListener rocketMQConsumerListener) { 50 | RocketMQConsumerConfig config = rocketMQConsumerListener.getConsumerConfig(); 51 | DefaultMQPushConsumer defaultMQPushConsumer = new DefaultMQPushConsumer(); 52 | defaultMQPushConsumer.setNamesrvAddr(nameSrvAddr); 53 | defaultMQPushConsumer.setConsumerGroup(config.getConsumerGroup()); 54 | Map> tags = config.getTags(); 55 | StringBuilder tagBuilder = new StringBuilder(); 56 | List tmpTags = new ArrayList<>(tags.keySet()); 57 | for (int i = 0; i < tmpTags.size(); i++) { 58 | if (tmpTags.contains("*") && tmpTags.size() > 1) { 59 | throw new IllegalArgumentException("订阅的tag不合法"); 60 | } 61 | tagBuilder.append(tmpTags.get(i)); 62 | if (tmpTags.size() > i + 1) { 63 | tagBuilder.append("||"); 64 | } 65 | } 66 | try { 67 | defaultMQPushConsumer.subscribe(config.getTopic(), tagBuilder.toString()); 68 | defaultMQPushConsumer.subscribe(config.getTopic(), "*"); 69 | } catch (MQClientException e) { 70 | throw new IllegalArgumentException("订阅语法错误", e); 71 | } 72 | defaultMQPushConsumer.setMessageModel(config.getMessageModel()); 73 | if (config.getConsumeThreadMax() == 0) { 74 | defaultMQPushConsumer.setConsumeThreadMax(this.consumeThreadMax); 75 | } else { 76 | defaultMQPushConsumer.setConsumeThreadMax(config.getConsumeThreadMax()); 77 | } 78 | if (config.getConsumeThreadMin() == 0) { 79 | defaultMQPushConsumer.setConsumeThreadMin(this.consumeThreadMin); 80 | } else { 81 | defaultMQPushConsumer.setConsumeThreadMin(config.getConsumeThreadMin()); 82 | } 83 | return defaultMQPushConsumer; 84 | } 85 | 86 | public List getAllMQPushConsumer() { 87 | return pushConsumers; 88 | } 89 | 90 | 91 | public SimpleListenerFactory getListenerFactory() { 92 | return listenerFactory; 93 | } 94 | 95 | 96 | @Override 97 | public void afterPropertiesSet() { 98 | pushConsumers = new ArrayList<>(); 99 | pushConsumerMap = new HashMap<>(16); 100 | if (listenerFactory == null) { 101 | listenerFactory = new SimpleListenerFactory(); 102 | listenerFactory.setApplicationContext(this.applicationContext); 103 | listenerFactory.afterPropertiesSet(); 104 | } 105 | listenerFactory.getAllListeners().forEach((topic, consumerListener) -> { 106 | DefaultMQPushConsumer pushConsumer = createDefaultMQPushConsumer(consumerListener); 107 | pushConsumers.add(pushConsumer); 108 | pushConsumerMap.put(topic, pushConsumer); 109 | }); 110 | } 111 | 112 | @Override 113 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 114 | this.applicationContext = applicationContext; 115 | } 116 | 117 | 118 | public void setConsumeThreadMin(int consumeThreadMin) { 119 | this.consumeThreadMin = consumeThreadMin; 120 | } 121 | 122 | 123 | public void setConsumeThreadMax(int consumeThreadMax) { 124 | this.consumeThreadMax = consumeThreadMax; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/SimpleListenerFactory.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | 4 | import org.rocketmq.starter.RocketMQConsumerListener; 5 | import org.rocketmq.starter.annotation.RocketMQListener; 6 | import org.rocketmq.starter.annotation.RocketMQMessage; 7 | import org.rocketmq.starter.exception.MethodNotSupportException; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.BeansException; 11 | import org.springframework.beans.factory.InitializingBean; 12 | import org.springframework.context.ApplicationContext; 13 | import org.springframework.context.ApplicationContextAware; 14 | import org.springframework.core.MethodIntrospector; 15 | import org.springframework.core.annotation.AnnotatedElementUtils; 16 | import org.springframework.util.CollectionUtils; 17 | 18 | import java.lang.reflect.Method; 19 | import java.util.Arrays; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | * @author He Jialin 25 | */ 26 | public class SimpleListenerFactory implements InitializingBean, ApplicationContextAware { 27 | 28 | 29 | private static final Logger logger = LoggerFactory.getLogger(SimpleListenerFactory.class); 30 | 31 | private Map allListeners; 32 | 33 | private MethodResolver resolver; 34 | 35 | private ApplicationContext context; 36 | 37 | SimpleListenerFactory() { 38 | this.resolver = new MethodResolver(); 39 | } 40 | 41 | private RocketMQConsumerListener createRocketMqConsumerListener(SubscriptionGroup subscriptionGroup) { 42 | RocketMQListenerMethodAdapter adapter = new RocketMQListenerMethodAdapter(subscriptionGroup); 43 | adapter.setInvoker(context.getBean(MethodInvoker.class)); 44 | return adapter; 45 | } 46 | 47 | public Map getAllListeners() { 48 | return allListeners; 49 | } 50 | 51 | @Override 52 | public void afterPropertiesSet() { 53 | allListeners = new HashMap<>(); 54 | Map subscriptionGroups = this.resolver.getSubscriptionGroups(); 55 | subscriptionGroups.forEach((topic, subscriptionGroup) -> 56 | allListeners.put(topic, createRocketMqConsumerListener(subscriptionGroup))); 57 | 58 | } 59 | 60 | @Override 61 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 62 | this.context = applicationContext; 63 | this.resolver.setApplicationContext(applicationContext); 64 | } 65 | 66 | 67 | private class MethodResolver implements ApplicationContextAware { 68 | 69 | private ApplicationContext context; 70 | 71 | private Map subscriptionGroups = new HashMap<>(); 72 | 73 | private boolean initSubscription = false; 74 | 75 | 76 | Map getSubscriptionGroups() { 77 | if (!initSubscription) { 78 | resolveListenerMethod(); 79 | } 80 | return subscriptionGroups; 81 | } 82 | 83 | void resolveListenerMethod() { 84 | context.getBeansWithAnnotation(RocketMQListener.class).forEach((beanName, obj) -> { 85 | Map annotatedMethods = MethodIntrospector.selectMethods(obj.getClass(), 86 | (MethodIntrospector.MetadataLookup) method -> AnnotatedElementUtils 87 | .findMergedAnnotation(method, RocketMQMessage.class)); 88 | initSubscriptionGroup(annotatedMethods, obj); 89 | }); 90 | this.initSubscription = true; 91 | } 92 | 93 | private void initSubscriptionGroup(Map annotatedMethod, Object target) { 94 | if (!CollectionUtils.isEmpty(annotatedMethod)) { 95 | annotatedMethod.forEach((method, listener) -> { 96 | validateMethod(method); 97 | RocketMQListener rocketMQListener = method.getDeclaringClass().getAnnotation(RocketMQListener.class); 98 | String topic = rocketMQListener.topic(); 99 | String tag = listener.tag(); 100 | if (subscriptionGroups.containsKey(topic)) { 101 | subscriptionGroups.get(topic).putTagToGroup(tag, method); 102 | } else { 103 | SubscriptionGroup subscriptionGroup = new SubscriptionGroup(topic); 104 | subscriptionGroup.putTagToGroup(tag, method); 105 | subscriptionGroup.setTarget(target); 106 | subscriptionGroups.put(topic, subscriptionGroup); 107 | } 108 | 109 | }); 110 | } 111 | 112 | } 113 | 114 | private void validateMethod(Method method) { 115 | if (method.getParameterCount() > 2) { 116 | throw new MethodNotSupportException("method: " + method + " 参数列表不被支持"); 117 | } 118 | boolean typeSupport = Arrays.stream(method.getParameterTypes()).allMatch(parmType -> parmType.equals(method 119 | .getAnnotation 120 | (RocketMQMessage.class).messageClass()) || parmType.equals(MessageContext.class)); 121 | if (!typeSupport) { 122 | throw new MethodNotSupportException("方法参数中含有不被支持的类型"); 123 | } 124 | } 125 | 126 | @Override 127 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 128 | this.context = applicationContext; 129 | } 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/consumer/SubscriptionGroup.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.consumer; 2 | 3 | import lombok.Data; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * 14 | * @author He Jialin 15 | */ 16 | @Data 17 | public class SubscriptionGroup { 18 | 19 | private String topic; 20 | 21 | private List tagList; 22 | 23 | private Object target; 24 | 25 | private Map tagMethods; 26 | 27 | SubscriptionGroup(String topic) { 28 | this.tagList = new ArrayList<>(); 29 | this.tagMethods = new HashMap<>(); 30 | this.topic = topic; 31 | } 32 | 33 | public void putTagToGroup(String tag, Method method) { 34 | if (tagList.contains(tag)) { 35 | throw new IllegalArgumentException("重复的消费者"); 36 | } 37 | tagList.add(tag); 38 | tagMethods.put(tag, method); 39 | } 40 | 41 | public Method getMethod(String tag) { 42 | return tagMethods.get(tag); 43 | } 44 | 45 | public Collection getAllMethods() { 46 | return tagMethods.values(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/producer/MessageProxy.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.producer; 2 | 3 | 4 | import org.apache.rocketmq.client.producer.MessageQueueSelector; 5 | import org.apache.rocketmq.client.producer.SendCallback; 6 | import org.apache.rocketmq.common.message.Message; 7 | 8 | import java.io.Serializable; 9 | 10 | /** 11 | * @author He Jialin 12 | */ 13 | public class MessageProxy implements Serializable { 14 | private static final long serialVersionUID = -3470788148313235550L; 15 | 16 | private MessageQueueSelector messageQueueSelector; 17 | 18 | private SendCallback sendCallback; 19 | 20 | private Message message; 21 | 22 | private Object selectorArg; 23 | 24 | public Object getSelectorArg() { 25 | return selectorArg; 26 | } 27 | 28 | public void setSelectorArg(Object selectorArg) { 29 | this.selectorArg = selectorArg; 30 | } 31 | 32 | public Message getMessage() { 33 | return message; 34 | } 35 | 36 | public void setMessage(Message message) { 37 | this.message = message; 38 | } 39 | 40 | public MessageQueueSelector getMessageQueueSelector() { 41 | return messageQueueSelector; 42 | } 43 | 44 | public void setMessageQueueSelector(MessageQueueSelector messageQueueSelector) { 45 | this.messageQueueSelector = messageQueueSelector; 46 | } 47 | 48 | public SendCallback getSendCallback() { 49 | return sendCallback; 50 | } 51 | 52 | public void setSendCallback(SendCallback sendCallback) { 53 | this.sendCallback = sendCallback; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/producer/NamedMessageQueueSelector.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.producer; 2 | 3 | import org.apache.rocketmq.client.producer.MessageQueueSelector; 4 | 5 | import org.springframework.beans.factory.BeanNameAware; 6 | 7 | /** 8 | * 9 | * @author He Jialin 10 | */ 11 | public interface NamedMessageQueueSelector extends MessageQueueSelector, BeanNameAware { 12 | 13 | String getBeanName(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/producer/RocketMQProducerConfig.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.producer; 2 | 3 | 4 | import org.rocketmq.starter.core.RocketMQConfig; 5 | 6 | /** 7 | * @author He Jialin 8 | */ 9 | public class RocketMQProducerConfig extends RocketMQConfig { 10 | 11 | private String producerGroup; 12 | 13 | private int timeOut = 3000; 14 | 15 | public String getProducerGroup() { 16 | return producerGroup; 17 | } 18 | 19 | public void setProducerGroup(String producerGroup) { 20 | this.producerGroup = producerGroup; 21 | } 22 | 23 | public int getTimeOut() { 24 | return timeOut; 25 | } 26 | 27 | public void setTimeOut(int timeOut) { 28 | this.timeOut = timeOut; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/producer/RocketMQProducerContainer.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.producer; 2 | 3 | 4 | import org.apache.rocketmq.client.exception.MQClientException; 5 | 6 | import org.rocketmq.starter.exception.ContatinerInitException; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.BeansException; 10 | import org.springframework.beans.factory.InitializingBean; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.context.ApplicationContextAware; 13 | import org.springframework.context.SmartLifecycle; 14 | 15 | import java.util.Map; 16 | 17 | /** 18 | * @author He Jialin 19 | */ 20 | 21 | public class RocketMQProducerContainer implements InitializingBean, ApplicationContextAware, SmartLifecycle { 22 | 23 | private static final Logger logger = LoggerFactory.getLogger(RocketMQProducerContainer.class); 24 | 25 | private final Object monitor = new Object(); 26 | 27 | private volatile boolean running = false; 28 | 29 | private volatile boolean initialized = false; 30 | 31 | private ApplicationContext applicationContext; 32 | 33 | @Override 34 | public void start() { 35 | if (initialized) { 36 | Map templateMap = applicationContext.getBeansOfType 37 | (RocketMQProducerTemplate.class); 38 | templateMap.forEach((beanName, template) -> { 39 | try { 40 | template.start(); 41 | } catch (ContatinerInitException e) { 42 | logger.error("bean {} is already start", beanName, e); 43 | } catch (MQClientException e) { 44 | e.printStackTrace(); 45 | } 46 | }); 47 | } 48 | this.running = true; 49 | } 50 | 51 | @Override 52 | public void stop() { 53 | Map templateMap = applicationContext.getBeansOfType 54 | (RocketMQProducerTemplate.class); 55 | templateMap.forEach((beanName, template) -> template.shutdown()); 56 | this.running = false; 57 | } 58 | 59 | @Override 60 | public boolean isRunning() { 61 | return running; 62 | } 63 | 64 | 65 | @Override 66 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 67 | this.applicationContext = applicationContext; 68 | } 69 | 70 | @Override 71 | public void afterPropertiesSet() { 72 | this.initialized = true; 73 | } 74 | 75 | @Override 76 | public boolean isAutoStartup() { 77 | return true; 78 | } 79 | 80 | @Override 81 | public void stop(Runnable callback) { 82 | stop(); 83 | callback.run(); 84 | } 85 | 86 | @Override 87 | public int getPhase() { 88 | return Integer.MAX_VALUE; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/core/producer/RocketMQProducerTemplate.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.core.producer; 2 | 3 | 4 | import org.apache.rocketmq.client.exception.MQBrokerException; 5 | import org.apache.rocketmq.client.exception.MQClientException; 6 | import org.apache.rocketmq.client.producer.DefaultMQProducer; 7 | import org.apache.rocketmq.client.producer.MessageQueueSelector; 8 | import org.apache.rocketmq.client.producer.SendCallback; 9 | import org.apache.rocketmq.client.producer.SendResult; 10 | import org.apache.rocketmq.common.message.Message; 11 | import org.apache.rocketmq.remoting.exception.RemotingException; 12 | import org.rocketmq.starter.core.RocketMQProducer; 13 | import org.rocketmq.starter.exception.ContatinerInitException; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import java.util.concurrent.atomic.AtomicBoolean; 17 | 18 | /** 19 | * @author He Jialin 20 | */ 21 | public class RocketMQProducerTemplate extends RocketMQProducerConfig implements RocketMQProducer { 22 | 23 | private static final Logger logger = LoggerFactory.getLogger(RocketMQProducerTemplate.class); 24 | 25 | private String namesrvAddr = System.getProperty("spring.rocketmq.namesrv.addr", System.getenv("NAMESRV_ADDR")); 26 | 27 | private DefaultMQProducer defaultMQProducer; 28 | 29 | private AtomicBoolean started = new AtomicBoolean(false); 30 | 31 | 32 | public void setNamesrvAddr(String namesrvAddr) { 33 | this.namesrvAddr = namesrvAddr; 34 | } 35 | 36 | @Override 37 | public SendResult send(Message message) throws InterruptedException, RemotingException, MQClientException, MQBrokerException { 38 | return this.defaultMQProducer.send(message); 39 | } 40 | 41 | @Override 42 | public void start() throws MQClientException { 43 | if (started.get()) { 44 | throw new ContatinerInitException("this templeate is already init"); 45 | } 46 | if (this.defaultMQProducer == null) { 47 | this.defaultMQProducer = new DefaultMQProducer(); 48 | } 49 | this.defaultMQProducer.setProducerGroup(this.getProducerGroup()); 50 | this.defaultMQProducer.setSendMsgTimeout(this.getTimeOut()); 51 | this.defaultMQProducer.setNamesrvAddr(this.namesrvAddr); 52 | this.defaultMQProducer.start(); 53 | this.started.set(true); 54 | } 55 | 56 | @Override 57 | public void shutdown() { 58 | if (started.get()) { 59 | this.defaultMQProducer.shutdown(); 60 | started.set(false); 61 | } 62 | } 63 | 64 | 65 | @Override 66 | public void send(MessageProxy messageProxy) throws MQClientException, InterruptedException, RemotingException { 67 | SendCallback sendCallback = messageProxy.getSendCallback() == null ? new DefaultSendCallback() : messageProxy 68 | .getSendCallback(); 69 | if (messageProxy.getMessage() == null) { 70 | throw new NullPointerException("the message is null"); 71 | } 72 | if (this.isOrderlyMessage()) { 73 | MessageQueueSelector selector = messageProxy.getMessageQueueSelector(); 74 | if (selector == null) { 75 | throw new NullPointerException("the sequential message must be configured with MessageQueueSelector."); 76 | } 77 | this.defaultMQProducer.send(messageProxy.getMessage(), selector, messageProxy.getSelectorArg(), 78 | sendCallback); 79 | } else { 80 | this.defaultMQProducer.send(messageProxy.getMessage(), sendCallback); 81 | } 82 | } 83 | 84 | private static class DefaultSendCallback implements SendCallback { 85 | 86 | @Override 87 | public void onSuccess(SendResult sendResult) { 88 | 89 | } 90 | 91 | @Override 92 | public void onException(Throwable e) { 93 | 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/exception/ConsumeException.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.exception; 2 | 3 | /** 4 | * @author He Jialin 5 | */ 6 | public class ConsumeException extends RuntimeException{ 7 | private static final long serialVersionUID = 4093867789628938836L; 8 | 9 | public ConsumeException(String message) { 10 | super(message); 11 | } 12 | 13 | public ConsumeException(Throwable cause) { 14 | super(cause); 15 | } 16 | 17 | public ConsumeException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/exception/ContatinerInitException.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.exception; 2 | 3 | /** 4 | * @author He Jialin 5 | */ 6 | public class ContatinerInitException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = 7065334226374339536L; 9 | 10 | public ContatinerInitException(String message) { 11 | super(message); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/exception/MethodNotSupportException.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.exception; 2 | 3 | /** 4 | * @author He Jialin 5 | */ 6 | public class MethodNotSupportException extends RuntimeException{ 7 | 8 | 9 | private static final long serialVersionUID = 5136128643850367154L; 10 | 11 | /** 12 | * Constructs a new runtime exception with {@code null} as its 13 | * detail message. The cause is not initialized, and may subsequently be 14 | * initialized by a call to {@link #initCause}. 15 | */ 16 | public MethodNotSupportException() { 17 | } 18 | 19 | /** 20 | * Constructs a new runtime exception with the specified detail message. 21 | * The cause is not initialized, and may subsequently be initialized by a 22 | * call to {@link #initCause}. 23 | * 24 | * @param message the detail message. The detail message is saved for 25 | * later retrieval by the {@link #getMessage()} method. 26 | */ 27 | public MethodNotSupportException(String message) { 28 | super(message); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/exception/NoListenerFoundException.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.exception; 2 | 3 | /** 4 | * @author He Jialin 5 | */ 6 | public class NoListenerFoundException extends RuntimeException{ 7 | 8 | private static final long serialVersionUID = -7909279829141688730L; 9 | 10 | /** 11 | * Constructs a new runtime exception with {@code null} as its 12 | * detail message. The cause is not initialized, and may subsequently be 13 | * initialized by a call to {@link #initCause}. 14 | */ 15 | public NoListenerFoundException() { 16 | } 17 | 18 | /** 19 | * Constructs a new runtime exception with the specified detail message. 20 | * The cause is not initialized, and may subsequently be initialized by a 21 | * call to {@link #initCause}. 22 | * 23 | * @param message the detail message. The detail message is saved for 24 | * later retrieval by the {@link #getMessage()} method. 25 | */ 26 | public NoListenerFoundException(String message) { 27 | super(message); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/extension/InitBean.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.extension; 2 | 3 | /** 4 | * 钩子接口,简单来说,钩子是用来挂东西的,通过在钩子可实现对方法的动态扩展 5 | * 6 | * @author He Jialin 7 | */ 8 | public interface InitBean { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/extension/InitBeanAware.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.extension; 2 | 3 | import org.springframework.beans.factory.Aware; 4 | 5 | /** 6 | * 在类进行实例化后对泛型所对应的钩子进行set注入 7 | * 8 | * @author He Jialin 9 | */ 10 | public interface InitBeanAware extends Aware { 11 | 12 | /** 13 | * 设置bean的钩子 14 | * @param initBean 15 | */ 16 | void setHook(K initBean); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/extension/InterceptorInitBean.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.extension; 2 | 3 | 4 | /** 5 | * 方法拦截钩子,在方法执行前后进行拦截 6 | * 7 | * @author He Jialin 8 | */ 9 | public interface InterceptorInitBean extends InitBean { 10 | 11 | /** 12 | * 方法执行前 13 | * 14 | * @param args 方法执行的参数 15 | * @return true: 正常执行 16 | * false: 阻止方法继续执行 17 | */ 18 | boolean preHandle(Object... args); 19 | 20 | /** 21 | * 方法执行后 22 | * @param methodSuccess : 方法是否执行成功 23 | * @param args 方法执行的参数 24 | * 25 | */ 26 | void nextHandle(boolean methodSuccess, Object... args); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/extension/InterceptorInitBeanAware.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.extension; 2 | 3 | /** 4 | * 实现这个接口用来注入一个方法拦截钩子的实例,默认为InterceptorHookSupport 5 | * 6 | * @author He Jialin 7 | */ 8 | public interface InterceptorInitBeanAware extends InitBeanAware { 9 | 10 | /** 11 | * set注入方法拦截钩子 12 | * 13 | * @param hook 方法拦截钩子 14 | */ 15 | @Override 16 | void setHook(InterceptorInitBean hook); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/extension/core/InitBeanFactory.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.extension.core; 2 | 3 | 4 | import org.rocketmq.starter.extension.InterceptorInitBeanAware; 5 | import org.rocketmq.starter.extension.InitBeanAware; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.beans.factory.InitializingBean; 8 | import org.springframework.context.ApplicationContext; 9 | import org.springframework.context.ApplicationContextAware; 10 | 11 | import java.util.Arrays; 12 | import java.util.Map; 13 | 14 | /** 15 | * @author He Jialin 16 | */ 17 | public class InitBeanFactory implements InitializingBean, ApplicationContextAware { 18 | 19 | private ApplicationContext context; 20 | 21 | private void awareHook() { 22 | InterceptorInitBeanSupport support = new InterceptorInitBeanSupport(); 23 | support.setApplicationContext(context); 24 | support.afterPropertiesSet(); 25 | Map awareMap = context.getBeansOfType(InitBeanAware.class); 26 | for (Map.Entry awareEntry : awareMap.entrySet()) { 27 | InitBeanAware aware = awareEntry.getValue(); 28 | if (Arrays.asList(aware.getClass().getInterfaces()).contains(InterceptorInitBeanAware.class)) { 29 | ((InterceptorInitBeanAware) aware).setHook(support); 30 | } 31 | } 32 | 33 | } 34 | 35 | @Override 36 | public void afterPropertiesSet() { 37 | awareHook(); 38 | } 39 | 40 | @Override 41 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 42 | this.context = applicationContext; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/extension/core/InterceptorInitBeanSupport.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.extension.core; 2 | 3 | import org.rocketmq.starter.extension.InterceptorInitBean; 4 | import org.springframework.beans.BeansException; 5 | import org.springframework.beans.factory.InitializingBean; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.context.ApplicationContextAware; 8 | 9 | import java.util.Map; 10 | 11 | /** 12 | * 方法拦截钩子的默认实现 13 | * 可通过继承此类,对插件和方法执行等细节进行覆盖和扩展 14 | * 继承的此类的类需要将类的实例交由Spring容器进行管理,并且配置为单例的 15 | * 16 | * @author He Jialin 17 | */ 18 | public class InterceptorInitBeanSupport implements InterceptorInitBean, ApplicationContextAware, InitializingBean { 19 | 20 | /** 21 | * 应用程序上下文,通过Spring自动注入 22 | */ 23 | private ApplicationContext applicationContext; 24 | 25 | /** 26 | * 当前钩子挂载的所有插件 27 | */ 28 | protected Map plugins; 29 | 30 | /** 31 | * 方法执行前的插件调用,默认为循环调用 32 | * 33 | * @param args 方法执行的参数 34 | */ 35 | @Override 36 | public boolean preHandle(Object... args) { 37 | for (InterceptorPlugin plugin : plugins.values()) { 38 | if (!plugin.preHandle(args)) { 39 | return false; 40 | } 41 | } 42 | return true; 43 | } 44 | 45 | /** 46 | * 方法执行后的插件调用,默认为循环调用 47 | * 48 | * @param args 方法执行的参数 49 | */ 50 | @Override 51 | public void nextHandle(boolean methodSuccess,Object... args) { 52 | plugins.values().forEach(plugin -> plugin.nextHandle(methodSuccess,args)); 53 | } 54 | 55 | @Override 56 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 57 | this.applicationContext = applicationContext; 58 | } 59 | 60 | /** 61 | * 加载插件实例 62 | * 63 | * @param clazz 插件类型 64 | * @return 插件Map, key为bean名称, value为插件实例对象 65 | */ 66 | protected Map getPlugins(Class clazz) { 67 | return applicationContext.getBeansOfType(clazz); 68 | } 69 | 70 | @Override 71 | public void afterPropertiesSet() { 72 | plugins = getPlugins(InterceptorPlugin.class); 73 | } 74 | 75 | 76 | /** 77 | * 78 | * @see InterceptorInitBean 79 | * 80 | * 插件接口,实现类需要交由Spring容器管理 81 | */ 82 | public interface InterceptorPlugin { 83 | 84 | boolean preHandle(Object... args); 85 | 86 | void nextHandle(boolean methodSuccess, Object... args); 87 | 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/operation/ContainerOperatorController.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.operation; 2 | 3 | 4 | import org.rocketmq.starter.ConsumerOperator; 5 | import org.rocketmq.starter.core.consumer.OperationResult; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | 12 | /** 13 | * 14 | * @author He Jialin 15 | */ 16 | 17 | @RestController 18 | public class ContainerOperatorController { 19 | 20 | private final ConsumerOperator operator; 21 | 22 | @Autowired 23 | public ContainerOperatorController(ConsumerOperator operator){ 24 | this.operator = operator; 25 | } 26 | 27 | @GetMapping("/consumer/resume/{topic}") 28 | private OperationResult resume(@PathVariable("topic") String topic){ 29 | return operator.resumeConsumer(topic); 30 | } 31 | 32 | 33 | @GetMapping("/consumer/suspend/{topic}") 34 | private OperationResult suspend(@PathVariable("topic") String topic){ 35 | return operator.suspendConsumer(topic); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/rocketmq/starter/suport/MessageConverter.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.starter.suport; 2 | 3 | /** 4 | * 5 | * @author He Jialin 6 | */ 7 | public interface MessageConverter { 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/org/rocketmq/spring/starter/RocketMQAutoConfigurationTests.java: -------------------------------------------------------------------------------- 1 | package org.rocketmq.spring.starter; 2 | 3 | import org.junit.runner.RunWith; 4 | 5 | /** 6 | *

7 | * Note: 8 | *

9 | * Date: 03/31/2018 16:41. 10 | * 11 | * @author He Jialin 12 | */ 13 | public class RocketMQAutoConfigurationTests { 14 | } 15 | --------------------------------------------------------------------------------