springboot-rocketMQ-stater源码解析及优雅的整合方式

1.springboot与rocketMQ优雅整合

在springboot中使用RocketMQ消息队列,可以使用rocketmq-spring-boot-starter的依赖进行整合,这种整合方式的好处是简单便捷,只需要简单配置就可以使用,同时也屏蔽了对RocketMQ Client API的了解,需要去了解封装后的API 。在学习RocketMQ的时候大家都会了解RocketMQ Client 的API ,再学封装后的API费时费力,同时也不利于对底层API的了解。下面介绍简单优雅的整合RocketMQ Client到SpringBoot项目中的方式,保留使用RocketMQ Client 原生API的方式。需要将RocketMQ的依赖添加到项目中,依赖如下:

<dependency>
   <groupId>org.apache.rocketmq</groupId>
     <artifactId>rocketmq-client</artifactId>
     <version>4.6.0</version>
 </dependency>

 <!--权限控制依赖包(非必须的依赖,使用到acl的时候才需要)-->
 <dependency>
     <groupId>org.apache.rocketmq</groupId>
     <artifactId>rocketmq-acl</artifactId>
     <version>4.6.0</version>
 </dependency>

配置整合代码如下:

@Configuration
public class RocketMQConfig{

    @Bean(initMethod = "start",destroyMethod = "shutdown")
    public DefaultMQProducer getProducer(){
        DefaultMQProducer producer = new DefaultMQProducer("hr_unique_test_group");
        producer.setNamesrvAddr("192.168.31.67:9876;192.168.31.68:9876");
        //当一个JVM启动多个produce的时候,通过实例名称区分
        //producer.setInstanceName("instance1");
        //producer.setRetryTimesWhenSendAsyncFailed();
        //producer.setRetryTimesWhenSendAsyncFailed();
        System.out.println("create producer ok");
        return  producer ;
    }


	@Bean(initMethod = "start" ,destroyMethod = "shutdown")
    public DefaultMQPushConsumer init() throws MQClientException {
        //下面使用的push的消费方式,pull的使用 new DefaultLitePullConsumer(),拉的方式需要自己保存偏移量和队列的关系
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("hr_consumer_test");
        consumer.setNamesrvAddr("192.168.31.67:9876;192.168.31.68:9876");
		consumer.setMessageModel(MessageModel.BROADCASTING);
        consumer.setConsumeMessageBatchMaxSize(2);

        // 设置消费的topic ,需要过滤的tag , * 代表不过滤
        consumer.subscribe("TopicTest", "TagSpring");
        //注册消费的方式:这里是并发消费 ,还可以设置顺序消费 MessageListenerOrderly
        consumer.registerMessageListener(new MessageListenerConcurrently() {

            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                            ConsumeConcurrentlyContext context) {
                //默认情况下,每次都只是一条数据
                for (MessageExt messageExt : msgs) {
                    System.out.println("收到消息:\t"+new String(messageExt.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        System.out.printf("Consumer Started.%n");
        return consumer;
    }

}

使用方式:

@Autowired
private DefaultMQProducer producer ;

@RequestMapping("/{key}")
public Object getResult(@PathVariable("key")String key) throws 
UnsupportedEncodingException, RemotingException, 
MQClientException, InterruptedException {
    Message message = new Message("TopicTest", "TagSpring", key, ("self spring RocketMQ msg " + key).getBytes(RemotingHelper.DEFAULT_CHARSET));
    producer.send(message, new SendCallback() {
        @Override
        public void onSuccess(SendResult sendResult) {
            System.out.println(JSON.toJSONString(sendResult));
        }

        @Override
        public void onException(Throwable throwable) {
            System.out.println("onException");
        }
    });
    System.out.println("send ok");
    return "ok";
}

以上整合方式是直接将生产者和消费者注册到Spring容器中,同时必须执行Bean初始化和销毁的时候分别调用 start() 和shutdown()方法。在配置类中,可以使用注入的方式将NamesrvAddr,topic等参数写在配置文件中进行管理。使用生产者的时候直接注入 DefaultMQProducer 生产者对象即可,保留使用原生API对象的方式进行消息的发送和消费。

2.rocketmq-spring-boot-starter源码解析

spring-boot-starter 是模块化编程的方式,添加依赖就可以实现自动装配,自动装配需要实现接口配在配置文件中,这就是模块化编程中提倡的面向接口编程。在 rocketmq-spring-boot-starter源码中 的rocketmq-spring-boot模块下,找到resources文件夹,下的META-INF文件夹下有一个spring.factories的文件。这里配置了自动装配的类,也就是这个模块被整合时候的程序入口。如下图,这是spring标准的配置整合方式
在这里插入图片描述

2.1 RocketMQAutoConfiguration自动装配入口类

查看RocketMQAutoConfiguration类,有一大堆注解进行了标注,如下:

@Configuration
@EnableConfigurationProperties(RocketMQProperties.class)
@ConditionalOnClass({MQAdmin.class})
@ConditionalOnProperty(prefix = "rocketmq", value = "name-server", matchIfMissing = true)
@Import({MessageConverterConfiguration.class, ListenerContainerConfiguration.class, ExtProducerResetConfiguration.class, RocketMQTransactionConfiguration.class})
@AutoConfigureAfter({MessageConverterConfiguration.class})
@AutoConfigureBefore({RocketMQTransactionConfiguration.class})

public class RocketMQAutoConfiguration {

@EnableConfigurationProperties(RocketMQProperties.class) 这个注解触发自动导入项目配置文件中RocketMQ的配置,封装到 RocketMQProperties这个类中注入到spring中,这个类中可以查看nameServer等配置项和默认配置项等参数。

@ConditionalOnClass({MQAdmin.class})
@ConditionalOnProperty(prefix = “rocketmq”, value = “name-server”, matchIfMissing = true)
这两个注解标注了触发这个模块自动装配的条件。

@Import({MessageConverterConfiguration.class, ListenerContainerConfiguration.class, ExtProducerResetConfiguration.class, RocketMQTransactionConfiguration.class})
@AutoConfigureAfter({MessageConverterConfiguration.class})
@AutoConfigureBefore({RocketMQTransactionConfiguration.class})
这三个注解标识了自动装配的时候需要初始化导入的类和,初始化的导入顺序。

2.2 整合生产者的源码分析

RocketMQAutoConfiguration入口类中可以看到如下两个方法

@Bean
@ConditionalOnMissingBean(DefaultMQProducer.class)
@ConditionalOnProperty(prefix = "rocketmq", value = {"name-server", "producer.group"})
public DefaultMQProducer defaultMQProducer(RocketMQProperties rocketMQProperties) {
    RocketMQProperties.Producer producerConfig = rocketMQProperties.getProducer();
    String nameServer = rocketMQProperties.getNameServer();
    String groupName = producerConfig.getGroup();
    Assert.hasText(nameServer, "[rocketmq.name-server] must not be null");
    Assert.hasText(groupName, "[rocketmq.producer.group] must not be null");

    String accessChannel = rocketMQProperties.getAccessChannel();

    String ak = rocketMQProperties.getProducer().getAccessKey();
    String sk = rocketMQProperties.getProducer().getSecretKey();
    boolean isEnableMsgTrace = rocketMQProperties.getProducer().isEnableMsgTrace();
    String customizedTraceTopic = rocketMQProperties.getProducer().getCustomizedTraceTopic();

    DefaultMQProducer producer = RocketMQUtil.createDefaultMQProducer(groupName, ak, sk, isEnableMsgTrace, customizedTraceTopic);

    producer.setNamesrvAddr(nameServer);
    if (!StringUtils.isEmpty(accessChannel)) {
        producer.setAccessChannel(AccessChannel.valueOf(accessChannel));
    }
    producer.setSendMsgTimeout(producerConfig.getSendMessageTimeout());
    producer.setRetryTimesWhenSendFailed(producerConfig.getRetryTimesWhenSendFailed());
    producer.setRetryTimesWhenSendAsyncFailed(producerConfig.getRetryTimesWhenSendAsyncFailed());
    producer.setMaxMessageSize(producerConfig.getMaxMessageSize());
    producer.setCompressMsgBodyOverHowmuch(producerConfig.getCompressMessageBodyThreshold());
    producer.setRetryAnotherBrokerWhenNotStoreOK(producerConfig.isRetryNextServer());

    return producer;
}
@Bean(destroyMethod = "destroy")
@ConditionalOnBean(DefaultMQProducer.class)
@ConditionalOnMissingBean(name = ROCKETMQ_TEMPLATE_DEFAULT_GLOBAL_NAME)
public RocketMQTemplate rocketMQTemplate(DefaultMQProducer mqProducer,
    RocketMQMessageConverter rocketMQMessageConverter) {
    RocketMQTemplate rocketMQTemplate = new RocketMQTemplate();
    rocketMQTemplate.setProducer(mqProducer);
    rocketMQTemplate.setMessageConverter(rocketMQMessageConverter.getMessageConverter());
    return rocketMQTemplate;
}

从上面两个方法中可以看到,public DefaultMQProducer defaultMQProducer(RocketMQProperties rocketMQProperties)这个方法先是创建了生产者对象注册到了spring容器中,这个方法又使用了@EnableConfigurationProperties(RocketMQProperties.class)注解引入的配置文件中的配置项来创建生产者。

public RocketMQTemplate rocketMQTemplate(DefaultMQProducer mqProducer, RocketMQMessageConverter rocketMQMessageConverter)方法则是使用上一个方法创建的 DefaultMQProducer 生产者对象和@Import注解引入的MessageConverterConfiguration对象。创建 RocketMQTemplate 对象封装 DefaultMQProducer 生产者对象 和 MessageConverterConfiguration对象,然后注册到容器中。这也就是为什么使用@Autowired注入RocketMQTemplate对象就能够发送消息的原因,其实就是进行简单封装后底层还是调用DefaultMQProducer 生产者对象发送消息。

看到这里,你是不是有点疑惑,创建RocketMQTemplate 的时候指定了销毁的时候@Bean(destroyMethod = “destroy”)指定了要调用的方法,实际还是调用producer的shutdown()方法释放资源,可是创建生产者的时候并没有指明调用 producer.start()方法?

查看 RocketMQTemplate 类 可以发送,这个类实现了InitializingBean这个接口,实现方法如下:

@Override
public void afterPropertiesSet() throws Exception {
    if (producer != null) {
        producer.start();
    }
}

看到这里就可以知道了,当创建完RocketMQTemplate这个类,设置完成属性后,会回调这个方法,这时候会调用生产者的start()方法,这时候就能使用RocketMQTemplate发送消息了。

2.3 整合消费者的源码解析

使用过 rocketmq-spring-boot-starter应该了解,实现消费者需要类实现RocketMQListener接口 并且要使用@RocketMQMessageListener标准消费者的信息 和@Service向Spring注册服务,就实现了消息消费的监听。

上面开始介绍入口的时候@Import 注解中有导入ListenerContainerConfiguration.class这个类,看类名就知道这个应该跟消息消费监听有关。查看这个类,空参构造方法没有进行相关初始化操作,但是类实现了两个接口如下:

@Configuration
public class ListenerContainerConfiguration implements ApplicationContextAware, SmartInitializingSingleton

ApplicationContextAware 接口可以获取到spring容器的上下文对象,SmartInitializingSingleton接口实现的方法在是会在这个类对象实例化完成后调用这个接口的方法的。ListenerContainerConfiguration这个类是@Configuration修饰的配置类,是由spring进行实例会操作管理的,所以会自动调用到SmartInitializingSingleton接口实现的方法:

public void afterSingletonsInstantiated() {
   Map<String, Object> beans = this.applicationContext.getBeansWithAnnotation(RocketMQMessageListener.class)
       .entrySet().stream().filter(entry -> !ScopedProxyUtils.isScopedTarget(entry.getKey()))
       .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

   beans.forEach(this::registerContainer);
}

这个方法中我们可以看到applicationContext.getBeansWithAnnotation(RocketMQMessageListener.class)获取到了Spring管理对象中被RocketMQMessageListener注解标注的所有对象。这也是我们上面说的实现消费者监听需要满足的三个条件中的两个:使用@RocketMQMessageListener注解 和使用@Service注解向Spring注册服务
向下查看代码,获取到Bean名称和Bean类的映射集合后循环调用registerContainer方法, 方法主要逻辑如下(被我去除了校验):

private void registerContainer(String beanName, Object bean) {
   Class<?> clazz = AopProxyUtils.ultimateTargetClass(bean);
   RocketMQMessageListener annotation = clazz.getAnnotation(RocketMQMessageListener.class);
   String containerBeanName = String.format("%s_%s", DefaultRocketMQListenerContainer.class.getName(),
       counter.incrementAndGet());
   GenericApplicationContext genericApplicationContext = (GenericApplicationContext) applicationContext;

   genericApplicationContext.registerBean(containerBeanName, DefaultRocketMQListenerContainer.class,
       () -> createRocketMQListenerContainer(containerBeanName, bean, annotation));
   DefaultRocketMQListenerContainer container = genericApplicationContext.getBean(containerBeanName,
       DefaultRocketMQListenerContainer.class);
   if (!container.isRunning()) {
       try {
           container.start();
       } catch (Exception e) {
           log.error("Started container failed. {}", container, e);
           throw new RuntimeException(e);
       }
   }
}

上面方法流程总结如下:

  1. 获取到RocketMQMessageListener注解
  2. 生成注入到Spring容器中的Bean的名字
  3. 向spring注册DefaultRocketMQListenerContainer类的Bean
  4. 获取到容器中的Bean,开启消费者监听

这里流程看上去似乎跟消费者有点关系,但是又不太有联系。

genericApplicationContext.registerBean(containerBeanName, DefaultRocketMQListenerContainer.class,() -> createRocketMQListenerContainer(containerBeanName, bean, annotation));
DefaultRocketMQListenerContainer container = genericApplicationContext.getBean(containerBeanName,DefaultRocketMQListenerContainer.class);

查看这两行代码,第一行注册Bean的时候要调用这个() -> createRocketMQListenerContainer(containerBeanName, bean, annotation)匿名的方法,这里是不会触发调用的,实际还没有初始化 DefaultRocketMQListenerContainer对象。

genericApplicationContext.getBean这里执行的时候会触发上面提到的那个方法,查看createRocketMQListenerContainer方法如下:

private DefaultRocketMQListenerContainer createRocketMQListenerContainer(String name, Object bean,
        RocketMQMessageListener annotation) {
    DefaultRocketMQListenerContainer container = new DefaultRocketMQListenerContainer();

    container.setRocketMQMessageListener(annotation);

    String nameServer = environment.resolvePlaceholders(annotation.nameServer());
    nameServer = StringUtils.isEmpty(nameServer) ? rocketMQProperties.getNameServer() : nameServer;
    String accessChannel = environment.resolvePlaceholders(annotation.accessChannel());
    container.setNameServer(nameServer);
    if (!StringUtils.isEmpty(accessChannel)) {
        container.setAccessChannel(AccessChannel.valueOf(accessChannel));
    }
    container.setTopic(environment.resolvePlaceholders(annotation.topic()));
    String tags = environment.resolvePlaceholders(annotation.selectorExpression());
    if (!StringUtils.isEmpty(tags)) {
        container.setSelectorExpression(tags);
    }
    container.setConsumerGroup(environment.resolvePlaceholders(annotation.consumerGroup()));
    if (RocketMQListener.class.isAssignableFrom(bean.getClass())) {
        container.setRocketMQListener((RocketMQListener) bean);
    } else if (RocketMQReplyListener.class.isAssignableFrom(bean.getClass())) {
        container.setRocketMQReplyListener((RocketMQReplyListener) bean);
    }
    container.setMessageConverter(rocketMQMessageConverter.getMessageConverter());
    container.setName(name);

    return container;
}

从这个方法中可以看到,从RocketMQMessageListener注解获取到了用户设置的参数值,如consumerGroup ,topic ,tags,nameServer等值,最后都被封装到了DefaultRocketMQListenerContainer 对象中,然后将这个对象注册到了spring容器中。重点要注意的代码如下, 注解标注的类(我们实现的消费者接口的类)被复制给了DefaultRocketMQListenerContainer 中的引用。

if (RocketMQListener.class.isAssignableFrom(bean.getClass())) {
	container.setRocketMQListener((RocketMQListener) bean);
} else if (RocketMQReplyListener.class.isAssignableFrom(bean.getClass())) {
    container.setRocketMQReplyListener((RocketMQReplyListener) bean);
}

这时候查看DefaultRocketMQListenerContainer 对象,发现它实现了我们上面提到的InitializingBean接口,DefaultRocketMQListenerContainer 对象所以初始化设置完成后会调用到afterPropertiesSet()方法,方法实现如下:

@Override
public void afterPropertiesSet() throws Exception {
    initRocketMQPushConsumer();
    this.messageType = getMessageType();
    this.methodParameter = getMethodParameter();
    log.debug("RocketMQ messageType: {}", messageType);
}

看方法名就知道第一行代码对消费者进行初始化,到这里终于找到消费者的影子了。initRocketMQPushConsumer()方法很长,我不贴出来了,方法里面创建消费者对象,并根据上面获取到的注解信息,设置订阅的topic 、过滤条件、广播消费还是并发消费等等。

查看代码中的一段如下:

 switch (consumeMode) {
     case ORDERLY:
         consumer.setMessageListener(new DefaultMessageListenerOrderly());
         break;
     case CONCURRENTLY:
         consumer.setMessageListener(new DefaultMessageListenrConcurrently());
         break;
     default:
         throw new IllegalArgumentException("Property 'consumeMode' was wrong.");
 }

这段代码中设置了消费的模式是顺序消费还是并发消费,具体的消费逻辑在这个两个类中实现。查看者两个类中的消费方法 consumeMessage 或者 onMessage方法,最终都是调用的 handleMessage(messageExt)这个方法进行消费。消费的逻辑又指向了rocketMQListener.onMessage(doConvertMessage(messageExt)) 或者rocketMQReplyListener.onMessage(doConvertMessage(messageExt))这就是我们要实现的就接口,这两个对象的复制就是上面提到的重点要关注的代码,将注解标注的类对象赋值给了这两个字段。通过多态的形式,调用接口方法,实际上就调用到了我们的实现类方法中进行消费者逻辑处理了。

3.手写rocketmq-spring-boot-starter原理

下面简单实现 rocketmq-spring-boot-starter原理的流程

3.1 定义配置类

@ConfigurationProperties(prefix = "rocketmq")
public class RocketMQProperties {

    private String nameServer;

    /**
     * Enum type for accessChannel, values: LOCAL, CLOUD
     */
    private String accessChannel;

    private Producer producer;

    public static class Producer{

        private Integer retryTimesWhenSendAsyncFailed ;

        private Integer maxMessageSize;

        private String  group;

        public Integer getRetryTimesWhenSendAsyncFailed() {
            return retryTimesWhenSendAsyncFailed;
        }

        public void setRetryTimesWhenSendAsyncFailed(Integer retryTimesWhenSendAsyncFailed) {
            this.retryTimesWhenSendAsyncFailed = retryTimesWhenSendAsyncFailed;
        }

        public Integer getMaxMessageSize() {
            return maxMessageSize;
        }

        public void setMaxMessageSize(Integer maxMessageSize) {
            this.maxMessageSize = maxMessageSize;
        }

        public String getGroup() {
            return group;
        }

        public void setGroup(String group) {
            this.group = group;
        }
    }

    public String getNameServer() {
        return nameServer;
    }

    public void setNameServer(String nameServer) {
        this.nameServer = nameServer;
    }

    public String getAccessChannel() {
        return accessChannel;
    }

    public void setAccessChannel(String accessChannel) {
        this.accessChannel = accessChannel;
    }

    public Producer getProducer() {
        return producer;
    }

    public void setProducer(Producer producer) {
        this.producer = producer;
    }
}

3.2 定义消费者注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RocketMQMsgListener {

    String nameAddr() ;

    String consumerGroup();

    String topic();

    String filterTag() default "*";

    boolean isOrderyConsume() default false;

    MessageModel messageModel() default MessageModel.CLUSTERING;

}

3.3 定义消费者实现接口

public interface MsgConsumerListener {

    void onMessage(String message);
}

3.4 定义自动装配入口类实现整合

自动装配类入口代码

@Configuration
@EnableConfigurationProperties(RocketMQProperties.class)
@ConditionalOnProperty(prefix = "rocketmq",value = "name-server",matchIfMissing = true)
@Import(RocketConsumerAutoConfig.class)
public class RocketMQConfiguration {

    @Bean(initMethod = "start" ,destroyMethod = "shutdown")
    @ConditionalOnProperty(prefix = "rocketmq",value = {"name-server","producer.group"})
    public DefaultMQProducer defaultMQProducer(RocketMQProperties properties){
        RocketMQProperties.Producer proconfig = properties.getProducer();
        DefaultMQProducer producer = new DefaultMQProducer(proconfig.getGroup());
        producer.setNamesrvAddr(properties.getNameServer());
        Integer maxMessageSize = proconfig.getMaxMessageSize();
        if (maxMessageSize != null && maxMessageSize > 0 ){
            producer.setMaxMessageSize(maxMessageSize);
        }

        Integer retryTimesWhenSendAsyncFailed = proconfig.getRetryTimesWhenSendAsyncFailed();
        if (retryTimesWhenSendAsyncFailed != null && retryTimesWhenSendAsyncFailed >0 ){
            producer.setRetryTimesWhenSendAsyncFailed(retryTimesWhenSendAsyncFailed);
        }

        return producer;
    }
}

消费者整合配置代码

@Configuration
public class RocketConsumerAutoConfig implements ApplicationContextAware , SmartInitializingSingleton {

    private ConfigurableApplicationContext context;

    private List<DefaultMQPushConsumer> consumerList = new ArrayList();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = (ConfigurableApplicationContext) applicationContext;
    }

    @Override
    public void afterSingletonsInstantiated() {
        //获取到所有注解标记的Bean
        Map<String, Object> beans = this.context.getBeansWithAnnotation(RocketMQMsgListener.class)
                .entrySet().stream().filter(entry -> !ScopedProxyUtils.isScopedTarget(entry.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

        //循环注册Bean
        beans.forEach(this::registerConsumer);
    }

    private AtomicLong counter = new AtomicLong(0);

    private void registerConsumer(String beanName ,Object bean){
        Class<?> clazz = bean.getClass();
        RocketMQMsgListener annotation = clazz.getAnnotation(RocketMQMsgListener.class);


        String containerBeanName = String.format("%s_%s", beanName, counter.incrementAndGet());
        GenericApplicationContext genericApplicationContext = (GenericApplicationContext) context;

        //注册消费者到容器中
        genericApplicationContext.registerBean(containerBeanName, DefaultMQPushConsumer.class,
                () -> createRocketMQConsumer(containerBeanName, bean, annotation));
        DefaultMQPushConsumer consumer = genericApplicationContext.getBean(containerBeanName,
                DefaultMQPushConsumer.class);
        try {
            consumer.start();
        } catch (MQClientException e) {
            e.printStackTrace();
            throw new RuntimeException("consumer.start fail");
        }
    }

    private DefaultMQPushConsumer createRocketMQConsumer(String containerBeanName,Object bean,RocketMQMsgListener annotation){
        String nameAddr = annotation.nameAddr();
        String consumerGroup = annotation.consumerGroup();
        String topic = annotation.topic();
        String tag = annotation.filterTag();
        MessageModel messageModel = annotation.messageModel();

        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup);
        consumer.setNamesrvAddr(nameAddr);
        try {
            consumer.subscribe(topic,tag);
            consumer.setMessageModel(messageModel);


        MsgConsumerListener listener = (MsgConsumerListener)bean;
        if (annotation.isOrderyConsume()){
            //顺序消费
            consumer.registerMessageListener(new MessageListenerOrderly() {
                @Override
                public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                    //默认情况下,每次都只是一条数据
                    for (MessageExt messageExt : msgs) {
                        System.out.println("顺序消费:\t"+ new String(messageExt.getBody()));
                        listener.onMessage(new String(messageExt.getBody()));
                    }

                    return ConsumeOrderlyStatus.SUCCESS;
                }
            });
        }else {
            //注册消费的方式:这里是并发消费 ,还可以设置顺序消费 MessageListenerOrderly
            consumer.registerMessageListener(new MessageListenerConcurrently() {

                @Override
                public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs,
                                                                ConsumeConcurrentlyContext context) {
                    //默认情况下,每次都只是一条数据
                    for (MessageExt messageExt : msgs) {
                        System.out.println("并发消费:\t"+new String(messageExt.getBody()));
                        listener.onMessage(new String(messageExt.getBody()));
                    }
                    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                }
            });
        }
        } catch (MQClientException e) {
            throw new RuntimeException("registerBean consumer fail");
        }
        consumerList.add(consumer);
        return consumer;
    }

    @PreDestroy
    private void destroy(){
        for (DefaultMQPushConsumer consumer : consumerList){
            System.out.println("shutdown");
            consumer.shutdown();
        }
    }
}

3.5编写配置文件

配置如下 resources\META-INF\spring.factories 固定路径和文件名,
配置中的key固定,value就是配置入口类的类名

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.heron.config.RocketMQConfiguration

在这里插入图片描述

3.6 pom文件依赖

   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.7.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>4.6.0</version>
        </dependency>

        <!--权限控制依赖包-->
        <dependency>
            <groupId>org.apache.rocketmq</groupId>
            <artifactId>rocketmq-acl</artifactId>
            <version>4.6.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

    </dependencies>

3.7 生产者和消费者使用方式

生产者使用:

@Autowired
private DefaultMQProducer producer

消费者使用:

@Service
@RocketMQMsgListener(topic = "testTopic", consumerGroup = "consumer", nameAddr= "192.168.31.67:9876;192.168.31.68:9876")
public class MyConsumer implements MsgConsumerListener{
    @Override
    public void onMessage(String message) {
        System.out.printf("------- StringConsumer received: %s \n", message);
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值