SpringBoot整合RocketMQ

326 阅读4分钟

按照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);
    }
}

如何处理各种消息类型

  1. 各种基础的消息发送机制参⻅单元测试类:com.roy.rocketmq.SpringRocketTest
  2. ⼀个RocketMQTemplate实例只能包含⼀个⽣产者,也就只能往⼀个Topic下发送消息。如果需要往另外⼀个Topic下发送消息,就需要通过@ExtRocketMQTemplateConfiguration()注解另外声明⼀个⼦类实例。
  3. 对于事务消息机制,最关键的事务监听器需要通过@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当中提供的拉模式客户端。