Spring Kafka @KafkaListener源码剖析

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 中。以下是重点部分的源码:

typescript 复制代码
// 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 的解析过程基本就清楚了,这部分流程的关键代码如下:

kotlin 复制代码
// 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);
         }
      }
   }

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

类名 作用
KafkaListenerAnnotationBeanPostProcessor Bean 后处理器,扫描 Spring 容器中的 Bean,解析带有 @KafkaListener 注解的方法,并将它们封装为 KafkaListenerEndpoint,最终注册到注册中心里面。
KafkaListenerEndpointRegistrar 端点注册器,工具类,收集所有 KafkaListenerEndpoint,并调用注册中心的注册方法。
KafkaListenerEndpointRegistry 端点注册中心,管理所有监听容器(MessageListenerContainer),负责创建和启动容器。
KafkaListenerContainerFactory 监听容器工厂,用于创建 MessageListenerContainer 实例。默认实现是 ConcurrentKafkaListenerContainerFactory。
MethodKafkaListenerEndpoint 封装了 @KafkaListener 注解信息(如 topics、groupId、配置等)以及目标方法,最终会基于它创建具体的 MessageListener。
MessageListenerContainer 消息监听容器接口,定义容器的生命周期(启动、停止)。
ConcurrentMessageListenerContainer 并发监听容器,内部包含多个 KafkaMessageListenerContainer 实例(根据 concurrency 参数),实现多线程消费。
KafkaMessageListenerContainer 单线程监听容器,负责实际与 Kafka 消费者交互、拉取消息并调用监听器。
ListenerConsumer KafkaMessageListenerContainer 的内部类,实际运行在独立线程中,执行消息拉取和处理的循环。
相关推荐
你驴我12 分钟前
WhatsApp Cloud API 速率限制下的令牌桶与退避策略实践
网络·后端·python
ClouGence34 分钟前
SAP HANA 到 Doris 数据迁移:4 种方案对比与迁移教程
数据库·后端·dba
LVZ36 分钟前
用了半年 AI 写代码,最烦的不是代码,是环境
前端·后端·开源
云边有个稻草人43 分钟前
从一条危险的 WHERE 条件说起:金仓数据库逻辑安全警示
后端
用户77139702070643 分钟前
从一脸懵到熟练使用:我的C#CAD二次开发日常中那些“反人类“的循环技巧
后端
JaneConan1 小时前
鸿蒙报错速查:arkts-no-any any 类型禁用,用了就炸,根因 + 真解法
后端·harmonyos
颜酱2 小时前
# 02 | 搭骨架:用 LangGraph 编排 12 步工作流(思路)
前端·人工智能·后端
颜酱2 小时前
02 | 搭骨架:用 LangGraph 编排 12 步工作流
前端·人工智能·后端
嘉伟桑3 小时前
Python调用电价API返回JSON示例:分时电价、现货电价和字段解析
后端
武子康3 小时前
Shippy:确定性工具、会话级 Sandbox 与 Live-Data Eval(4 类收敛 + 7 步实现方案 + 6 类评测指标)
前端·人工智能·后端