Spring Kafka @KafkaListener源码剖析

0 阅读5分钟

在 Spring Boot 中集成 Kafka,通过 @KafkaListener 注解可快速实现消息消费的编码开发,这种通过声明式的方式极大简化了消息监听器的配置,提升了开发效率。这里尝试通过分析 @KafkaListener 的关键源码,弄清楚它背后的原理。

一、@KafkaListener 处理的入口:Bean 后处理器

先简单说下 Spring 的 Bean 后处理器——BeanPostProcessor。BeanPostProcessor 是 Spring 中的一个接口,它有 2 个方法:

BeanPostProcessor

postProcessBeforeInitialization 方法会在 Bean 初始化之前被 Spring 调用,postProcessAfterInitialization 方法是在 Bean 初始化之后被调用。

Spring 在启动过程中会在 Bean 初始化完成后,对所有 Bean 进行扫描,对实现了 BeanPostProcessor 接口的 Bean 会调用其 postProcessAfterInitialization 方法。

二、@KafkaListener 注解解析,封装为 EndPoint

@KafkaListener 的处理就是在一个 BeanPostProcessor 中——KafkaListenerAnnotationBeanPostProcessor。KafkaListenerAnnotationBeanPostProcessor 在其 postProcessAfterInitialization 方法中会检查当前 bean 上或 bean 中的方法上是否标注了@KafkaListener 或@KafkaListeners 注解。

当检测到注解时,KafkaListenerAnnotationBeanPostProcessor 会解析注解属性,如 topics、groupId、containerFactory 等,并基于这些信息创建 MethodKafkaListenerEndpoint 实例。Endpoint 类封装了消息监听方法的相关元数据,包括方法对象、Bean 实例、并发配置和异常处理策略等。随后,这些端点(endpoint)会被包装成 EndpointDescriptor 放入到 KafkaListenerEndpointRegistrar 中。以下是重点部分的源码:

// KafkaListenerAnnotationBeanPostProcessor
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
      // 跳过代码
         Class<?> targetClass = AopUtils.getTargetClass(bean);
         // 查找类上有@KafkaListener注解的bean
         Collection<KafkaListener> classLevelListeners = this.findListenerAnnotations(targetClass);
         Map<Method, Set<KafkaListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass, (methodx) -> {
            // 查找方法上有@KafkaListener注解的bean
            Set<KafkaListener> listenerMethods = this.findListenerAnnotations(methodx);
            return !listenerMethods.isEmpty() ? listenerMethods : null;
         });
         boolean hasClassLevelListeners = !classLevelListeners.isEmpty();
         boolean hasMethodLevelListeners = !annotatedMethods.isEmpty();
         if (!hasMethodLevelListeners && !hasClassLevelListeners) {
            this.nonAnnotatedClasses.add(bean.getClass());
            this.logger.trace(() -> "No @KafkaListener annotations found on bean type: " + String.valueOf(bean.getClass()));
         } else {
            if (hasMethodLevelListeners) {
               for(Map.Entry<Method, Set<KafkaListener>> entry : annotatedMethods.entrySet()) {
                  Method method = (Method)entry.getKey();

                  for(KafkaListener listener : (Set)entry.getValue()) {
                     // 解析标记有@KafkaListener的方法,得到的信息封装为endpoint
                     this.processKafkaListener(listener, method, bean, beanName);
                  }
               }
               // 跳过代码
            }

            if (hasClassLevelListeners) {
               // @KafkaListener类标记有@KafkaHandler的方法也是监听方法
               Set<Method> methodsWithHandler = MethodIntrospector.selectMethods(targetClass, (methodx) -> AnnotationUtils.findAnnotation(methodx, KafkaHandler.class) != null);
               if (methodsWithHandler.isEmpty()) {
                  throw new IllegalStateException("No Kafka listener methods in bean: " + String.valueOf(bean));
               }

               List<Method> multiMethods = new ArrayList(methodsWithHandler);
               // 也是对方法做解析,封装为endpoint
               this.processMultiMethodListeners(classLevelListeners, multiMethods, targetClass, bean, beanName);
            }
      }

      return bean;
   }

   protected void processKafkaListener(KafkaListener kafkaListener, Method method, Object bean, String beanName) {
      try {
         this.globalLock.lock();
         Method methodToUse = this.checkProxy(method, bean);
         // 创建endpoint对象用于下面进行封装
         MethodKafkaListenerEndpoint<K, V> endpoint = new MethodKafkaListenerEndpoint();
         endpoint.setMethod(methodToUse);
         // 解析注解上的属性,set到endpoint中
         this.processMainAndRetryListeners(kafkaListener, bean, beanName, endpoint, methodToUse, (Class)null);
      } finally {
         this.globalLock.unlock();
      }

   }


  private final KafkaListenerEndpointRegistrar registrar = new KafkaListenerEndpointRegistrar();

   protected void processListener(MethodKafkaListenerEndpoint<?, ?> endpoint, KafkaListener kafkaListener, Object bean, String beanName, String[] topics, TopicPartitionOffset[] tps) {
      this.processKafkaListenerAnnotation(endpoint, kafkaListener, bean, topics, tps);
      String containerFactory = this.resolve(kafkaListener.containerFactory());
      KafkaListenerContainerFactory<?> listenerContainerFactory = this.resolveContainerFactory(kafkaListener, containerFactory, beanName);
      if (listenerContainerFactory instanceof ShareKafkaListenerContainerFactory) {
         endpoint.setShareConsumer(true);
      }
       // 这里将endpoint放入到 KafkaListenerEndpointRegistrar
      this.registrar.registerEndpoint(endpoint, listenerContainerFactory);
   }

三、创建 ListenerContainer

KafkaListenerAnnotationBeanPostProcessor 不仅实现了 BeanPostProcessor 的 postProcessAfterInitialization 方法,它还实现了 SmartInitializingSingleton 接口的 afterSingletonsInstantiated 方法。在 Spring 的初始化过程中,afterSingletonsInstantiated 是在所有 bean 都执行完 postProcessAfterInitialization 后调用的。

这里简单提下 Spring 容器启动过程中的 bean 初始化顺序,大致为:

  • 实例化(构造 bean 对象)
  • 属性填充(依赖注入)
  • 初始化前回调(postProcessBeforeInitialization,实现 BeanPostProcessor 接口)
  • 初始化(afterPropertiesSet / init-method,实现 InitializingBean 接口或@PostConstruct 注解的方法)
  • 初始化后回调(postProcessAfterInitialization,实现 BeanPostProcessor 接口)

当所有单例 Bean 都完成了上述 5 步后,Spring 才会触发:

  • 所有单例初始化完成后回调(afterSingletonsInstantiated,实现 SmartInitializingSingleton 接口)

上面都执行完成后,Spring bean 的初始化过程就完成了,后面就会触发启动实现了 SmartLifecycle 接口并且 autoStartup 为 true 的 bean,这个流程下面分析源码也会需要用到。

所以,KafkaListenerAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法中解析完所有 @KafkaListener 类或方法后封装为 endpoint,会触发 KafkaListenerAnnotationBeanPostProcessor 的 afterSingletonsInstantiated。这个方法中最重要的一点就是遍历所有 endpoint,构造消息监听器容器 ListenerContainer。

构造的 MessageListenerContainer 会注册到 KafkaListenerEndpointRegistry 中(也就是放入 KafkaListenerEndpointRegistry 的 map 中),而 KafkaListenerEndpointRegistry 实现了 SmartLifeCycle 接口,所以在注册完成后,会触发 KafkaListenerEndpointRegistry 的 start 方法。

KafkaListenerEndpointRegistry 的 start 方法中会根据并发度创建多个 KafkaMessageListenerContainer,每个 KafkaMessageListenerContainer 会再被调用 start 方法。

四、创建 ListenerConsumer

KafkaMessageListenerContainer 的 start 方法中会创建 ListenerConsumer。ListenerConsumer 它实际是运行在独立线程中,执行 Kafka 消息的拉取和处理循环,它的内部涉及对底层 Kafka-client 的 API 的封装。

这样整个 @KafkaListener 的解析过程基本就清楚了,这部分流程的关键代码如下:

// KafkaListenerAnnotationBeanPostProcessor
public void afterSingletonsInstantiated() {
      // 跳过代码
      // registrar虽然实现了InitializingBean接口,但它并没有注册为bean,所以这里手动调用
      // 这个方法的实现是直接调用的registerAllEndpoints方法进行endpoint注册
      this.registrar.afterPropertiesSet();
      // 跳过代码
   }

// ------------

// KafkaListenerEndpointRegistrar
   protected void registerAllEndpoints() {
      try {
         this.endpointsLock.lock();

         for(KafkaListenerEndpointDescriptor descriptor : this.endpointDescriptors) {
            // 跳过代码
            if (this.endpointRegistry != null) {
               // 这里基于endpoint创建MessageListenerContainer,
               // 这里的endpointRegistry就是KafkaListenerEndpointRegistry,endpointRegistry实现了SmartLifecycle接口,
               // 所以会在注册完成后触发KafkaListenerEndpointRegistry的start方法,start方法中就是基于MessageListenerContainer创建kafka的ListenerConsumer了
               this.endpointRegistry.registerListenerContainer(descriptor.endpoint, this.resolveContainerFactory(descriptor));
            }
         }

         this.startImmediately = true;
      } finally {
         this.endpointsLock.unlock();
      }

   }


public void registerListenerContainer(KafkaListenerEndpoint endpoint, KafkaListenerContainerFactory<?> factory, boolean startImmediately) {
      // 跳过代码
      this.containersLock.lock();

      try {
         Assert.state(!this.listenerContainers.containsKey(id), "Another endpoint is already registered with id '" + id + "'");
         // 基于endpint创建消息监听器容器,然后放入到当前registry的listenerContainers中  
         MessageListenerContainer container = this.createListenerContainer(endpoint, factory);
         this.listenerContainers.put(id, container);
         // 跳过代码
      } finally {
         this.containersLock.unlock();
      }

   }


// ------------
// KafkaListenerEndpointRegistry
   public void start() {
      for (MessageListenerContainer listenerContainer : getListenerContainers()) {
         startIfNecessary(listenerContainer);
      }
      this.running = true;
   }

   private void startIfNecessary(MessageListenerContainer listenerContainer) {
      if ((this.contextRefreshed && this.alwaysStartAfterRefresh) || listenerContainer.isAutoStartup()) {
            // 调用MessageListenerContainer 的start方法,创建KafkaMessageListenerContainer
         listenerContainer.start();
      }
   }


// ------------
// ConcurrentMessageListenerContainer
    protected void doStart() {
      if (!isRunning()) {
         // 跳过代码

         // 根据并发度决定创建多少个KafkaMessageListenerContainer
         for (int i = 0; i < this.concurrency; i++) {
            KafkaMessageListenerContainer<K, V> container =
                  constructContainer(containerProperties, topicPartitions, i);
            configureChildContainer(i, container);
            if (isPauseRequested()) {
               container.pause();
            }
            container.start(); // KafkaMessageListenerContainer的start方法中会创建ListenerConsumer
            this.containers.add(container);
         }
      }
   }

五、整个流程的关键类及作用

类名作用
KafkaListenerAnnotationBeanPostProcessorBean 后处理器,扫描 Spring 容器中的 Bean,解析带有 @KafkaListener 注解的方法,并将它们封装为 KafkaListenerEndpoint,最终注册到注册中心里面。
KafkaListenerEndpointRegistrar端点注册器,工具类,收集所有 KafkaListenerEndpoint,并调用注册中心的注册方法。
KafkaListenerEndpointRegistry端点注册中心,管理所有监听容器(MessageListenerContainer),负责创建和启动容器。
KafkaListenerContainerFactory监听容器工厂,用于创建 MessageListenerContainer 实例。默认实现是 ConcurrentKafkaListenerContainerFactory。
MethodKafkaListenerEndpoint封装了 @KafkaListener 注解信息(如 topics、groupId、配置等)以及目标方法,最终会基于它创建具体的 MessageListener。
MessageListenerContainer消息监听容器接口,定义容器的生命周期(启动、停止)。
ConcurrentMessageListenerContainer并发监听容器,内部包含多个 KafkaMessageListenerContainer 实例(根据 concurrency 参数),实现多线程消费。
KafkaMessageListenerContainer单线程监听容器,负责实际与 Kafka 消费者交互、拉取消息并调用监听器。
ListenerConsumerKafkaMessageListenerContainer 的内部类,实际运行在独立线程中,执行消息拉取和处理的循环。