按照SpringBoot三板斧,快速创建RocketMQ的客户端。创建Maven⼯程,引⼊关键依赖:
<dependencies>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-spring-boot-starter</artifactId>
<version>2.3.1</version>
<exclusions>
<exclusion>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.rocketmq</groupId>
<artifactId>rocketmq-client</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
SpringBoot升级到了3.0.4版本后,JDK要升级到17以上
启动类
@SpringBootApplication
public class RocketMQSBApplication {
public static void main(String[] args) {
SpringApplication.run(RocketMQSBApplication.class,args);
}
}
配置⽂件:
rocketmq.name-server=192.168.65.112:9876
rocketmq.producer.group=springBootGroup
#如果这⾥不配,那就需要在消费者的注解中配。
#rocketmq.consumer.topic=
rocketmq.consumer.group=testGroup
server.port=9000
接下来就可以声明⽣产者,直接使⽤RocketMQTemplate进⾏消息发送。
package com.roy.rocketmq.basic;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.rocketmq.spring.support.RocketMQHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class SpringProducer {
@Resource
private RocketMQTemplate rocketMQTemplate;
public void sendMessage(String topic,String msg){
this.rocketMQTemplate.convertAndSend(topic,msg);
}
}
rocketMQTemplate不光可以发消息,还可以主动拉消息。
拉取消息时,需要配置rocketmq.consumer.topic和rocketmq.consumer.group参数
消费者的声明也很简单。所有属性通过@RocketMQMessageListener注解声明。
@Component
@RocketMQMessageListener(consumerGroup = "MyConsumerGroup", topic = "TestTopic",consumeMode=
ConsumeMode.CONCURRENTLY,messageModel= MessageModel.BROADCASTING)
public class SpringConsumer implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
System.out.println("Received message : "+ message);
}
}
如何处理各种消息类型
- 各种基础的消息发送机制参⻅单元测试类:com.roy.rocketmq.SpringRocketTest
- ⼀个RocketMQTemplate实例只能包含⼀个⽣产者,也就只能往⼀个Topic下发送消息。如果需要往另外⼀个Topic下发送消息,就需要通过@ExtRocketMQTemplateConfiguration()注解另外声明⼀个⼦类实例。
- 对于事务消息机制,最关键的事务监听器需要通过@RocketMQTransactionListener注解注⼊到Spring容器当中。在这个注解当中可以通过rocketMQTemplateBeanName属性,指向具体的RocketMQTemplate⼦类。
实现原理
RocketMQTemplate
RocketMQTemplate的注⼊过程参⻅org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration.
Push模式消费者
Push模式对于@RocketMQMessageListener注解的处理⽅式,⼊⼝在rocketmq-spring-boot-2.3.1.jar中的org.apache.rocketmq.spring.autoconfigure.ListenerContainerConfiguration类中。
这个ListenerContainerConfiguration配置类会往Spring容器中注⼊⼀个RocketMQMessageListenerContainerRegistrar对象。
@Configuration
@ConditionalOnMissingBean(RocketMQMessageListenerContainerRegistrar.class)
public class ListenerContainerConfiguration {
@Bean
public RocketMQMessageListenerContainerRegistrar rocketMQMessageListenerContainerRegistrar(RocketMQMessageConverter
rocketMQMessageConverter, ConfigurableEnvironment environment, RocketMQProperties rocketMQProperties) {
return new RocketMQMessageListenerContainerRegistrar(rocketMQMessageConverter, environment,
rocketMQProperties);
}
}
注⼊RocketMQMessageListenerContainerRegistrar后,rocketmq-spring-boot-2.3.1.jar中会另外注⼊⼀个RocketMQMessageListenerBeanPostProcessor对象。这个对象继承了SmartLifecycle接⼝,因此会在初始化完成后,调⽤他的start⽅法。在这⾥会调⽤RocketMQMessageListenerContainerRegistrar的startContainer⽅法。
@Override
public void start() {
if (!isRunning()) {
this.setRunning(true);
listenerContainerRegistrar.startContainer();
}
}
在这个⽅法中,会启动⼀个DefaultRocketMQListenerContainer。
public void startContainer() {
for (DefaultRocketMQListenerContainer container : containers) {
if (!container.isRunning()) {
try {
container.start();
} catch (Exception e) {
log.error("Started container failed. {}", container, e);
throw new RuntimeException(e);
}
}
}
}
这⾥这个DefaultRocketMQListenerContainer实际上就是对RocketMQ的DefaultMQPushConsumer进⾏封装的⼀个容器。 start⽅法实际上就是在启动⼀个RocketMQ的原⽣Consumer。
⾄于如何创建Consumer实例,⽅法就在DefaultRocketMQListenerContainer的afterPropertiesSet⽅法中。其中有个initRocketMQPushConsuer⽅法,就是在创建原⽣Consuer实例。
registerContainer的⽅法挺⻓的,我这⾥截取出跟今天的主题相关的⼏⾏重要的源码:
这其中最关注的,当然是创建容器的createRocketMQListenerContainer⽅法中。⽽在这个⽅法中,你基本看不到RocketMQ的原⽣API,都是在创建并维护⼀个DefaultRocketMQListenerContainer对象。⽽这个DefaultRocketMQListenerContainer类,就是我们今天关注的重点。
DefaultRocketMQListenerContainer类实现了InitializingBean接⼝,⾃然要先关注他的afterPropertiesSet⽅法。这是Spring提供的对象初始化的扩展机制。
public void afterPropertiesSet() throws Exception {
initRocketMQPushConsumer();
this.messageType = getMessageType();
this.methodParameter = getMethodParameter();
log.debug("RocketMQ messageType: {}", messageType);
}
这个⽅法就是⽤来初始化RocketMQ消费者的。在这个⽅法⾥就会创建⼀个RocketMQ原⽣的DefaultMQPushConsumer消费者。同样,⽅法很⻓,抽取出⽐较关注的重点源码。
private void initRocketMQPushConsumer() throws MQClientException {
.....
//检查并创建consumer对象。
if (Objects.nonNull(rpcHook)) {
consumer = new DefaultMQPushConsumer(consumerGroup, rpcHook, new AllocateMessageQueueAveragely(),
enableMsgTrace, this.applicationContext.getEnvironment().
resolveRequiredPlaceholders(this.rocketMQMessageListener.customizedTraceTopic()));
consumer.setVipChannelEnabled(false);
} else {
log.debug("Access-key or secret-key not configure in " + this + ".");
consumer = new DefaultMQPushConsumer(consumerGroup, enableMsgTrace,
this.applicationContext.getEnvironment().
resolveRequiredPlaceholders(this.rocketMQMessageListener.customizedTraceTopic()));
}
// 定制instanceName,有没有很熟悉!!!
consumer.setInstanceName(RocketMQUtil.getInstanceName(nameServer));
.....
//设定⼴播消费还是集群消费。
switch (messageModel) {
case BROADCASTING:
consumer.setMessageModel(org.apache.rocketmq.common.protocol.heartbeat.MessageModel.BROADCASTING);
break;
case CLUSTERING:
consumer.setMessageModel(org.apache.rocketmq.common.protocol.heartbeat.MessageModel.CLUSTERING);
break;
default:
throw new IllegalArgumentException("Property 'messageModel' was wrong.");
}
//维护消费者的其他属性。
...
//指定Consumer的消费监听 --》在消费监听中就会去调⽤onMessage⽅法。
switch (consumeMode) {
case ORDERLY:
consumer.setMessageListener(new DefaultMessageListenerOrderly());
break;
case CONCURRENTLY:
consumer.setMessageListener(new DefaultMessageListenerConcurrently());
break;
default:
throw new IllegalArgumentException("Property 'consumeMode' was wrong.");
}
}
这整个就是在维护RocketMQ的原⽣消费者对象。其中的使⽤⽅式,其实有很多地⽅是很值得借鉴的,尤其是消费监听的处理。
Pull模式
Pull模式的实现其实是通过在RocketMQTemplate实例中注⼊⼀个DefaultLitePullConsumer实例来实现的。只要注⼊并启动了这个DefaultLitePullConsumer示例后,后续就可以通过template实例的receive⽅法,来调⽤DefaultLitePullConsumer的poll⽅法,主动去Pull获取消息了。
初始化DefaultLitePullConsumer的代码依然是在rocketmq-spring-boot-2.3.1.jar包中。不过处理类是org.apache.rocketmq.spring.autoconfigure.RocketMQAutoConfiguration。这个配置类会配置在jar包中的spring.factories⽂件中,通过SpringBoot的⾃动装载机制加载进来。
@Bean(CONSUMER_BEAN_NAME)
@ConditionalOnMissingBean(DefaultLitePullConsumer.class)
@ConditionalOnProperty(prefix = "rocketmq", value = {"name-server", "consumer.group", "consumer.topic"}) //解析的
springboot配置属性。
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");
...
//创建消费者
DefaultLitePullConsumer litePullConsumer = RocketMQUtil.createDefaultLitePullConsumer(nameServer, accessChannel, groupName, topicName, messageModel, selectorType, selectorExpression, ak, sk, pullBatchSize, useTLS);
litePullConsumer.setEnableMsgTrace(consumerConfig.isEnableMsgTrace());
litePullConsumer.setCustomizedTraceTopic(consumerConfig.getCustomizedTraceTopic());
litePullConsumer.setNamespace(consumerConfig.getNamespace());
return litePullConsumer;
}
RocketMQUtil.createDefaultLitePullConsumer⽅法中,就是在维护⼀个DefaultLitePullConsumer实例。这个实例就是RocketMQ的原⽣API当中提供的拉模式客户端。