SpringBoot整合RabbitMq源码解析(正菜)

1,309 阅读5分钟

1:SpringBoot使用RabbitMq的终极方案

  • 使用 @RabbitListener来监听消息队列消息

2:如何开始分析 @RabbitListener 源码呢??

  • 既然我们是要分析 @RabbitListener,那么入口点当然就是它了,点进去一看,坏了,没有什么重要的线索

  • 注解原理
    • 注解本身并没有什么作用,只是起到一个标志的作用,比如Spring中一个A类,只要你给它加上一层Spring注解,那么它就会被Spring管理,也就是Spring扫描到了这个类上有这个注解,才会进行后续处理
    • 所以我们可以想到到底是哪个类会对 @RabbitListener 这个注解进行处理呢??
    • 一般开源项目都会这个注解进行一番解释,包括哪个类对它进行处理,可以看到处理 @RabbitListener 这个注解的就是 RabbitListenerAnnotationBeanPostProcessor ,既然让我们找到了线索,怎么能放过呢

3:开始上车

  • 第一眼我就看到了 BeanPostProcessor 接口,因为Spring源码我是了解过的,大家只要知道实现了这个接口,那么在这个Bean实例化之后就会调用它的二个接口 postProcessBeforeInitializationpostProcessAfterInitialization

  • 这是postProcessAfterInitialization的实现,这个方法的目的是:找到有 @RabbitListener 注解的类

        @Override
        public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
            Class<?> targetClass = AopUtils.getTargetClass(bean);
            final TypeMetadata metadata = this.typeCache.computeIfAbsent(targetClass, this::buildMetadata);
            for (ListenerMethod lm : metadata.listenerMethods) {
                for (RabbitListener rabbitListener : lm.annotations) {
                    //核心方法
                    processAmqpListener(rabbitListener, lm.method, bean, beanName);
                }
            }
            if (metadata.handlerMethods.length > 0) {
                processMultiMethodListeners(metadata.classAnnotations, metadata.handlerMethods, bean, beanName);
            }
            return bean;
        }
        
    
  • processAmqpListener(rabbitListener, lm.method, bean, beanName);解析

    protected Collection<Declarable> processAmqpListener(RabbitListener rabbitListener, Method method, Object bean,
               String beanName) {
           Method methodToUse = checkProxy(method, bean);
           MethodRabbitListenerEndpoint endpoint = new MethodRabbitListenerEndpoint();
           //重点1:这个是我们标注了 **@RabbitListener** 的方法,可以看下图,这个设置方法属性后面有大用
           endpoint.setMethod(methodToUse);
           return processListener(endpoint, rabbitListener, bean, methodToUse, beanName);
       }
       
    

  • processListener(endpoint, rabbitListener, bean, methodToUse, beanName);

     //这个方法就是从 **@RabbitListener** 注解中获取配置的属性,然后设置到MethodRabbitListenerEndpoint中去
     protected Collection<Declarable> processListener(MethodRabbitListenerEndpoint endpoint,
                RabbitListener rabbitListener, Object bean, Object target, String beanName) {
    
            final List<Declarable> declarables = new ArrayList<>();
            endpoint.setBean(bean);
            endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
            endpoint.setId(getEndpointId(rabbitListener));
            //设置我们要监听的队列
            endpoint.setQueueNames(resolveQueues(rabbitListener, declarables));
            endpoint.setConcurrency(resolveExpressionAsStringOrInteger(rabbitListener.concurrency(), "concurrency"));
            endpoint.setBeanFactory(this.beanFactory);
            endpoint.setReturnExceptions(resolveExpressionAsBoolean(rabbitListener.returnExceptions()));
            resolveErrorHandler(endpoint, rabbitListener);
            String group = rabbitListener.group();
            if (StringUtils.hasText(group)) {
                Object resolvedGroup = resolveExpression(group);
                if (resolvedGroup instanceof String) {
                    endpoint.setGroup((String) resolvedGroup);
                }
            }
            String autoStartup = rabbitListener.autoStartup();
            if (StringUtils.hasText(autoStartup)) {
                endpoint.setAutoStartup(resolveExpressionAsBoolean(autoStartup));
            }
    
            endpoint.setExclusive(rabbitListener.exclusive());
            String priority = resolveExpressionAsString(rabbitListener.priority(), "priority");
            if (StringUtils.hasText(priority)) {
                try {
                    endpoint.setPriority(Integer.valueOf(priority));
                }
                catch (NumberFormatException ex) {
                    throw new BeanInitializationException("Invalid priority value for " +
                            rabbitListener + " (must be an integer)", ex);
                }
            }
    
            resolveExecutor(endpoint, rabbitListener, target, beanName);
            resolveAdmin(endpoint, rabbitListener, target);
            resolveAckMode(endpoint, rabbitListener);
            resolvePostProcessor(endpoint, rabbitListener, target, beanName);
            resolveMessageConverter(endpoint, rabbitListener, target, beanName);
            resolveReplyContentType(endpoint, rabbitListener);
            RabbitListenerContainerFactory<?> factory = resolveContainerFactory(rabbitListener, target, beanName);
            //核心方法,注册MethodRabbitListenerEndpoint
            this.registrar.registerEndpoint(endpoint, factory);
            return declarables;
        }
    
  • this.registrar.registerEndpoint(endpoint, factory);

    //这个方法是为了注册一个新的 RabbitListenerContainerFactory,因为我们需要一个监听队列消息的监听容器,所以就需要一个工厂类来创建一个监听容器
    public void registerEndpoint(RabbitListenerEndpoint endpoint,
                @Nullable RabbitListenerContainerFactory<?> factory) {
            Assert.notNull(endpoint, "Endpoint must be set");
            Assert.hasText(endpoint.getId(), "Endpoint id must be set");
            Assert.state(!this.startImmediately || this.endpointRegistry != null, "No registry available");
            // Factory may be null, we defer the resolution right before actually creating the container
            AmqpListenerEndpointDescriptor descriptor = new AmqpListenerEndpointDescriptor(endpoint, factory);
            synchronized (this.endpointDescriptors) {
                if (this.startImmediately) { // Register and start immediately
                    //核心方法
                    this.endpointRegistry.registerListenerContainer(descriptor.endpoint, // NOSONAR never null
                            resolveContainerFactory(descriptor), true);
                }
                else {
                    this.endpointDescriptors.add(descriptor);
                }
            }
        }
        
    
  • this.endpointRegistry.registerListenerContainer(descriptor.endpoint, resolveContainerFactory(descriptor), true);

     //创建一个消息监听容器                 
     public void registerListenerContainer(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory<?> factory,
     		boolean startImmediately) {
         Assert.notNull(endpoint, "Endpoint must not be null");
         Assert.notNull(factory, "Factory must not be null");
    
         String id = endpoint.getId();
         Assert.hasText(id, "Endpoint id must not be empty");
         synchronized (this.listenerContainers) {
             Assert.state(!this.listenerContainers.containsKey(id),
                     "Another endpoint is already registered with id '" + id + "'");
             //看到这个有没有很熟悉,在之前的文章我们就是使用 MessageListenerContainer 来监听消息队列中的消息的       
             MessageListenerContainer container = createListenerContainer(endpoint, factory);
             this.listenerContainers.put(id, container);
             if (StringUtils.hasText(endpoint.getGroup()) && this.applicationContext != null) {
                 List<MessageListenerContainer> containerGroup;
                 if (this.applicationContext.containsBean(endpoint.getGroup())) {
                     containerGroup = this.applicationContext.getBean(endpoint.getGroup(), List.class);
                 }
                 else {
                     containerGroup = new ArrayList<MessageListenerContainer>();
                     this.applicationContext.getBeanFactory().registerSingleton(endpoint.getGroup(), containerGroup);
                 }
                 containerGroup.add(container);
             }
             if (this.contextRefreshed) {
                 container.lazyLoad();
             }
             if (startImmediately) {
                 startIfNecessary(container);
             }
         }
    

  • 看到这里返回一个MessageListenerContainer,然后回去看一下如何使用 SimpleMessageListenerContainer来监听消息,看到这里是不是对于之后的源码分析就有一个大概的思路了,为了方便,我把之前的demo搬过来了

    • 我们可以大胆预测下接下来会做什么

    • 1:得到了MessageListenerContainer之后,我们会给它设置各种各样的属性,最重要的就是我们要监听的队列的名称了

    • 2:最最重要的就是设置消息监听的MessageListener对象了,这个对像是可以从消息队列中拿到消息的,然后异步调用它的 onMessage方法,得到队列中的消息

      @Bean
        public SimpleMessageListenerContainer listenerContainer(ConnectionFactory connectionFactory) {
            SimpleMessageListenerContainer messageListenerContainer = new SimpleMessageListenerContainer(connectionFactory);
            messageListenerContainer.setQueueNames("queue.order");
            //设置并发消费者数量
            messageListenerContainer.setConcurrentConsumers(3);
            messageListenerContainer.setMaxConcurrentConsumers(5);
            //设置消息自动确认机制
            messageListenerContainer.setAcknowledgeMode(AcknowledgeMode.AUTO);
            messageListenerContainer.setMessageListener(new MessageListener() {
                @Override
                public void onMessage(Message message) {
                    log.info("消息内容为:{}",new String(message.getBody()));
               }
            });
            //消费端限流
            messageListenerContainer.setPrefetchCount(1);
            return messageListenerContainer;
        }
      

4:装逼时刻到了

  • MessageListenerContainer container = createListenerContainer(endpoint, factory);

    protected MessageListenerContainer createListenerContainer(RabbitListenerEndpoint endpoint,
                RabbitListenerContainerFactory<?> factory) {
            //核心方法
            MessageListenerContainer listenerContainer = factory.createListenerContainer(endpoint);
    
            if (listenerContainer instanceof InitializingBean) {
                try {
                    ((InitializingBean) listenerContainer).afterPropertiesSet();
                }
                catch (Exception ex) {
                    throw new BeanInitializationException("Failed to initialize message listener container", ex);
                }
            }
    
            int containerPhase = listenerContainer.getPhase();
            if (containerPhase < Integer.MAX_VALUE) {  // a custom phase value
                if (this.phase < Integer.MAX_VALUE && this.phase != containerPhase) {
                    throw new IllegalStateException("Encountered phase mismatch between container factory definitions: " +
                            this.phase + " vs " + containerPhase);
                }
                this.phase = listenerContainer.getPhase();
            }
    
            return listenerContainer;
        }
        
    
  • MessageListenerContainer listenerContainer = factory.createListenerContainer(endpoint);

    • 进入这个方法你会发现代码好长,暂时我们只需要了解一个大概流程就好,至于细节可以以后慢慢分析,我们可以在这个方法中找到这样一行代码 endpoint.setupListenerContainer(instance); 这个方法是设置消息监听容器的
  • endpoint.setupListenerContainer(instance);

      public void setupListenerContainer(MessageListenerContainer listenerContainer) {
             AbstractMessageListenerContainer container = (AbstractMessageListenerContainer) listenerContainer;
    
             boolean queuesEmpty = getQueues().isEmpty();
             boolean queueNamesEmpty = getQueueNames().isEmpty();
             if (!queuesEmpty && !queueNamesEmpty) {
                 throw new IllegalStateException("Queues or queue names must be provided but not both for " + this);
             }
             if (queuesEmpty) {
                 Collection<String> names = getQueueNames();
                 //设置我们要监听的队列
                 container.setQueueNames(names.toArray(new String[names.size()]));
             }
             else {
                 Collection<Queue> instances = getQueues();
                 container.setQueues(instances.toArray(new Queue[instances.size()]));
             }
    
             container.setExclusive(isExclusive());
             if (getPriority() != null) {
                 Map<String, Object> args = container.getConsumerArguments();
                 args.put("x-priority", getPriority());
                 container.setConsumerArguments(args);
             }
             //黄色至RabbitAdmin
             if (getAdmin() != null) {
                 container.setAmqpAdmin(getAdmin());
             }
             //核心:设置MessageListener
             setupMessageListener(listenerContainer);
         }
         
    
  • 为什么说 **setupMessageListener(listenerContainer);**是核心呢,因为我们是要依靠 MessageListener 来监听队列消息的呀

    private void setupMessageListener(MessageListenerContainer container) {
          MessageListener messageListener = createMessageListener(container);
          Assert.state(messageListener != null, () -> "Endpoint [" + this + "] must provide a non null message listener");
          //设置MessageListener
          container.setupMessageListener(messageListener);
      }
      
    
  • 注意,这里的 MessageListenerMessagingMessageListenerAdapter这个实例,但是也有 onMessage这个方法

      @Override
         public void onMessage(org.springframework.amqp.core.Message amqpMessage, Channel channel) throws Exception { // NOSONAR
             Message<?> message = toMessagingMessage(amqpMessage);
             invokeHandlerAndProcessResult(amqpMessage, channel, message);
         }
         
    

5:中场休息

  • 源码部分到这要结束了,不禁会有人说:

  • 这就结束了,好像什么也没讲一样呀,是的,那接下来就说一下如何处理消息

6:消息处理

  • 我们在MessagingMessageListenerAdapteronMessage这个方法打上断点,为什么在这里打断点是因为消息队列如果有消息就会执行这个方法然后进行后续的处理,不信我们就试试,我们尝试往RabbitMq发送一条消息

  • 之后会将我们的消息封装成一个 Message对象

  • invokeHandlerAndProcessResult(amqpMessage, channel, message);这个方法就是会调用我们有 @RabbitListener 这个注解的方法,还记得在开始处理 @RabbitListener这个注解的使用就已经设置了标记的方法嘛,就是这时候用的,它底层就是利用反射机制调用了我们的方法,并且将 包含了消息内容的 Message对象一起传递过去,这也是为什么我们自定义的 handleMessage 能够拿到消息的原理

7:结束语

  • 至此,@RabbitListener 注解的底层原理就分析到这里了,我只是分析了个大概的流程,其实还有很多可以研究的地方,比如:
    • 1:@RabbitListener 为什么能够帮我们创建队列,交换机,绑定关系等
    • 2:ConnectionFactory,RabbitAdminSimpleMessageListenerContainer这个Bean是什么时候创建的等等,这些就跟SpringBoot的自动装配有关了