夯实 kafka 系列|第六章:自界说注解 @EvalEventListener 开发

[复制链接]
发表于 2025-10-6 18:45:44 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
夯实 kafka 系列|第六章:自界说注解 @EvalEventListener 开发



1.媒介

在文章 《夯实 kafka 系列|第五章:基于 kafka 分布式事故框架 eval-event》 留了一个功能未实现


  • @EvalEventListener 监听分布式事故
本文来讨论这个自界说注解怎样开发。
源码已更新到 github


  • https://github.com/huajiexiewenfeng/eval-event
2.需求

通过 @EvalEventListener  监听到分布式事故 MyEvent(自界说事故)
  1. @EvalEventListener
  2. public void onEvalEvent(MyEvent event){
  3.     ...
  4. }
复制代码
3.思绪

3.1 @kafkaListener 注解的实现

我们先看看 kafka 官方怎样实现 @kafkaListener,然后参照这个来举行实现。
3.1.1 KafkaListenerAnnotationBeanPostProcessor(重点)



  • 作用:在 Spring 容器初始化时扫描全部 Bean,辨认带有 @KafkaListener 注解的方法。
  • 流程:

    • 遍历全部 Bean 的类方法。
    • 通过反射查抄方法是否标注 @KafkaListener。
    • 将符合条件的监听方法封装为 MethodKafkaListenerEndpoint。

3.1.2 监听器端点注册



  • 将分析后的端点信息注册到 KafkaListenerEndpointRegistry(管理全部监听容器的中央注册表)。
3.1.3 监听器容器创建



  • 容器工厂 ConcurrentKafkaListenerContainerFactory


  • 作用:根据 @KafkaListener 的设置创建 ConcurrentMessageListenerContainer。
3.1.4 动态署理天生(重点)



  • 对带有 @KafkaListener 的方法天生动态署理,使其具备消息处理惩罚本领。
3.1.5 总结

一共分为三步


  • 在 Bean 的初始化后生命周期阶段,找到全部标注 @KafkaListener 的方法和类
  • 整理好全部须要的元数据信息(Bean Method topic 等等),用于动态署理+斲丧 kafka 消息
  • 动态署理,实验类的方法,即标注 @KafkaListener 的方法
3.2 参考实现

我们大抵实现流程如下:


  • EvalEventListenerAnnotationBeanPostProcessor 实现 BeanPostProcessor
  • 遍历全部 Bean 的类方法,通过反射查抄方法是否标注 @EvalEventListener
  • 找到 topic 和 class 映射关系
  • 启动线程,拉取 topic 消息
  • 找到 @EvalEventListener 对应的 class,以及 method
  • 反射实验目的类的 method 方法
4.开发

3.1 @EvalEventListener 注解

  1. package com.csdn.event.kafka.annotation;
  2. import java.lang.annotation.*;
  3. @Target({ElementType.METHOD})
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @Documented
  6. public @interface EvalEventListener {
  7.     /**
  8.      * 指定监听的事件类型(默认根据方法参数推断)
  9.      */
  10.     Class<?>[] eventType() default {};
  11. }
复制代码
3.2 EvalEventAnnotationDefinition 注解界说

用于存储 Bean Method topic 等等信息
  1. public class EvalEventAnnotationDefinition {
  2.     private String topic;
  3.     // 事件的目标类
  4.     private Class<?> targetClass;
  5.     // 事件的类型
  6.     private Class<?> eventClass;
  7.     private Method method;
  8.     ...
  9. }
复制代码
3.3 EvalEventListenerAnnotationBeanPostProcessor 初始化后处理惩罚器



  • 找到标注 @EvalEventListener 类和方法
  • 循环实验 processEvalEventListener 方法

    • 封装 EvalEventAnnotationDefinition 信息
    • 启动 EvalEventListenerAnnotationThread 线程

  1. /**
  2. * This class processes beans after their initialization to find methods annotated with
  3. * {@link EvalEventListener} and register them as event listeners.
  4. */
  5. @Component
  6. public class EvalEventListenerAnnotationBeanPostProcessor<T extends EvalEvent> implements BeanPostProcessor, ApplicationContextAware {
  7.     ...
  8.     @Override
  9.     public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
  10.         Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
  11.         Map<Method, Set<EvalEventListener>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
  12.                 (MethodIntrospector.MetadataLookup<Set<EvalEventListener>>) method -> {
  13.                     Set<EvalEventListener> listenerMethods = findListenerAnnotations(method);
  14.                     return (!listenerMethods.isEmpty() ? listenerMethods : null);
  15.                 });
  16.         if (annotatedMethods.isEmpty()) {
  17.             // No methods found
  18.             logger.debug("No @EvalEventListener annotations found on bean '{}'", beanName);
  19.         } else {
  20.             // Non-empty set of methods
  21.             for (Map.Entry<Method, Set<EvalEventListener>> entry : annotatedMethods.entrySet()) {
  22.                 Method method = entry.getKey();
  23.                 for (EvalEventListener listener : entry.getValue()) {
  24.                     processEvalEventListener(listener, method, bean);
  25.                 }
  26.             }
  27.             logger.debug(" {} @KafkaListener methods processed on bean '{}'"
  28.                     , annotatedMethods.size(), beanName + ": " + annotatedMethods);
  29.         }
  30.         return bean;
  31.     }
  32.     private void processEvalEventListener(EvalEventListener listener, Method method, Object bean) {
  33.         // topic
  34.         Class<?>[] eventTypeArr = listener.eventType();
  35.         Class<?> eventType = eventTypeArr.length > 0 ? eventTypeArr[0] : null;
  36.         if (eventType == null) {
  37.             // 从方法参数中获取
  38.             eventType = method.getParameterTypes()[0];
  39.         }
  40.         if (eventType == null) {
  41.             throw new IllegalStateException("Event type not specified for @EvalEventListener on method: " + method);
  42.         }
  43.         try {
  44.             Object o = eventType.newInstance();
  45.             if (!(o instanceof EvalEvent)) {
  46.                 throw new IllegalStateException("Event type must be a subclass of EvalEvent: " + eventType);
  47.             }
  48.             EvalEvent event = (EvalEvent) o;
  49.             EvalEventAnnotationDefinition evalEventDefinition = new EvalEventAnnotationDefinition(event.getTopic(), bean.getClass(), eventType, method);
  50.             EvalEventListenerAnnotationThread<T> evalEventListenerThread =
  51.                     new EvalEventListenerAnnotationThread<>(evalEventDefinition, eventKafkaConsumerFactory, applicationContext);
  52.             evalEventListenerThread.start();
  53.         } catch (Exception e) {
  54.             throw new RuntimeException(e);
  55.         }
  56.     }
  57. ...
  58. }
复制代码
3.4 EvalEventListenerAnnotationThread 斲丧逻辑



  • 创建KafkaConsumer
  • 拉取 kafka 消息
  • 动态署理实验目的类的方法
  • ack 提交
  1. public class EvalEventListenerAnnotationThread<T extends EvalEvent> extends Thread {
  2. ...
  3.     @Override
  4.     public void run() {
  5.         // 1. 创建KafkaConsumer
  6.         KafkaConsumer<String, ?> consumer;
  7.         try {
  8.             consumer = eventKafkaConsumerFactory.buildKafkaConsumer(evalEventAnnotationDefinition.getTargetClass());
  9.             List<String> topicList = new ArrayList<>();
  10.             topicList.add(evalEventAnnotationDefinition.getTopic());
  11.             consumer.subscribe(topicList);
  12.         } catch (Exception e) {
  13.             log.error("KafkaConsumer构造失败", e);
  14.             e.printStackTrace();
  15.             return;
  16.         }
  17.         // 2. 消费消息
  18.         try {
  19.             while (true) {
  20.                 try {
  21.                     // 3. 拉取消息
  22.                     ConsumerRecords<String, ?> records = consumer.poll(
  23.                             Duration.ofMillis(500));
  24.                     if (records.isEmpty()) {
  25.                         continue;
  26.                     }
  27.                     // 4. 处理消息
  28.                     dispatch(records);
  29.                     // 5. 使用异步提交规避阻塞
  30.                     consumer.commitAsync();
  31.                 } catch (Exception e) {
  32.                     log.error("消息处理异常", e);
  33.                 }
  34.             }
  35.         } finally {
  36.             try {
  37.                 // 6.最后一次提交使用同步阻塞式提交
  38.                 consumer.commitSync();
  39.             } finally {
  40.                 consumer.close();
  41.             }
  42.         }
  43.     }
  44.     private void dispatch(ConsumerRecords<String, ?> records) {
  45.         for (ConsumerRecord<String, ?> record : records) {
  46.             try {
  47.                 Object data = record.value();
  48.                 if (data == null) {
  49.                     log.warn("接收到空消息记录,跳过处理");
  50.                     continue;
  51.                 }
  52.                 // 转换事件数据为指定类型
  53.                 T event = (T) ConvertUtil.convertEvent(data, evalEventAnnotationDefinition.getEventClass());
  54.                 if (event == null) {
  55.                     log.error("事件数据转换失败,跳过处理");
  56.                     continue;
  57.                 }
  58.                 Class<?> targetClass = evalEventAnnotationDefinition.getTargetClass();
  59.                 Method method = evalEventAnnotationDefinition.getMethod();
  60.                 if (targetClass == null || method == null) {
  61.                     log.error("目标类或方法为空,无法处理事件");
  62.                     continue;
  63.                 }
  64.                 // 从 Spring 容器获取 bean 实例
  65.                 Object target;
  66.                 try {
  67.                     // 先尝试从 Spring 容器获取
  68.                     target = applicationContext.getBean(targetClass);
  69.                 } catch (NoSuchBeanDefinitionException e) {
  70.                     // 如果容器中没有,尝试创建新实例
  71.                     log.warn("Spring 容器中未找到 {} 的实例,将创建新实例", targetClass.getSimpleName());
  72.                     target = targetClass.getDeclaredConstructor().newInstance();
  73.                     // 可选:如果需要依赖注入,可以在这里自动装配
  74.                     autowireBean(target);
  75.                 }
  76.                 method.setAccessible(true);
  77.                 // 调用目标方法
  78.                 method.invoke(target, event);
  79.             } catch (InstantiationException | IllegalAccessException e) {
  80.                 log.error("创建目标类实例失败: {}", e.getMessage(), e);
  81.             } catch (NoSuchMethodException e) {
  82.                 log.error("找不到目标类的无参构造方法: {}", e.getMessage(), e);
  83.             } catch (InvocationTargetException e) {
  84.                 log.error("方法调用失败: {}", e.getTargetException().getMessage(), e.getTargetException());
  85.             } catch (Exception e) {
  86.                 log.error("事件处理失败: {}", e.getMessage(), e);
  87.             }
  88.         }
  89.     }
  90. ...
  91. }
复制代码
5.测试

5.1 测试代码

  1. @Component
  2. public class UserAnnotationEventListener  {
  3.     @EvalEventListener
  4.     public void onEvent(UserCreatedEvent event) {
  5.         System.out.println("Received event: " + JSONObject.toJSONString(event));
  6.     }
  7. }
复制代码
5.2 源码调试

断点1 EvalEventListenerAnnotationBeanPostProcessor


  • 扫描到 UserAnnotationEventListener 以及对应的方法

断点2


  • 封装 EvalEventAnnotationDefinition

断点3 EvalEventListenerAnnotationThread


  • 拉取 kafka 消息

断点4 EvalEventListenerAnnotationThread 动态署理实验方法

5.3 测试结果


回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表