自学内容网 自学内容网

Spring事件机制@EventListener

@EventListener 看起来只是加了一个注解,Spring 到底在哪一刻、用什么机制把「事件」准确送到「标了注解的方法」里?
下面按 启动期 → 运行期 两条时间线,用“源码级”但“人话版”方式把完整链路拆开。
(代码行号基于 Spring Framework 6.1.x,5.x 原理完全一致)


一、启动期:把带 @EventListener 的方法“扫描”成“监听器”

  1. 入口:EventListenerMethodProcessor

    • 实现了 SmartInitializingSingleton,容器在最后阶段会回调它的 afterSingletonsInstantiated()
    • 核心逻辑:
      遍历所有已创建的单例 Bean
        → 反射拿到所有方法
        → 标了 @EventListener 的方法包装成 ApplicationListener 对象
        → 注册到 ApplicationContext 的监听器列表里
      
  2. 关键源码片段(缩略)

public void afterSingletonsInstantiated() {
    for (String beanName : beanFactory.getSingletonNames()) {
        Class<?> targetType = beanFactory.getType(beanName);
        // 1. 找到所有 @EventListener 方法
        Map<Method, EventListener> annotatedMethods =
                MethodIntrospector.selectMethods(targetType,
                    (MethodIntrospector.MetadataLookup<EventListener>) method ->
                            AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
        // 2. 每个方法包一层 ApplicationListener
        annotatedMethods.forEach((method, ann) -> {
            ApplicationListener<?> listener =
                    createApplicationListener(beanName, targetType, method, ann);
            // 3. 扔进容器
            applicationContext.addApplicationListener(listener);
        });
    }
}
  1. 包装细节:ApplicationListenerMethodAdapter
    • 把「方法对象 + 所在 Bean + 解析出来的条件表达式」缓存起来,等待运行期调用。
    • 如果方法上还有 @Async,会再包一层 AsyncExecutionInterceptor
      如果有 @TransactionalEventListener,会换成 TransactionalApplicationListener 实现类。

二、运行期:一次 publishEvent() 的完整旅程

  1. 发布入口
ApplicationEventPublisher.publishEvent(Object event)
└→ AbstractApplicationContext.multicastEvent(ApplicationEvent, ResolvableType)
  1. 获取线程池(默认同步)
Executor executor = getTaskExecutor();   // 默认 null

如果配置了 SimpleApplicationEventMulticaster#setTaskExecutor(executor),就会异步。

  1. 遍历所有已注册的 ApplicationListener

    • 对于 @EventListener 方式,此时拿到的就是前面扫描生成的 ApplicationListenerMethodAdapter
    • 先执行 supportsEventType()快速类型过滤
      把方法形参类型与事件实际类型做一次 ResolvableType.isAssignableFrom() 判断,不匹配直接跳过。
    • 再执行 supportsSource()源对象过滤(极少用)。
    • 如果注解里写了 condition = "#event.xxx",则走 SpEL 计算:
      EvaluationContext context = new MethodBasedEvaluationContext(...);
      boolean pass = expressionParser.parseExpression(condition).getValue(context, Boolean.class);
      
  2. 真正调用

    • 反射 method.invoke(bean, event)
    • 如果方法返回类型不是 void,Spring 会把返回值再当成一个新事件做一次 publishEvent()(事件链)。
    • 如果方法标了 @Async,此时会触发 AsyncExecutionInterceptor,把调用封装成 Runnable 丢进线程池。
  3. 异常处理

    • 同步调用:异常直接抛给发布者。
    • 异步调用:异常被线程池捕获,通过 AsyncUncaughtExceptionHandler 处理,默认只打日志。

三、事务监听 @TransactionalEventListener 的特殊分支

  1. 启动期同样由 EventListenerMethodProcessor 识别,但会创建 TransactionalApplicationListener 实例。
  2. 运行期并不立即执行,而是:
    publishEvent() 只是把监听器挂到 TransactionSynchronizationManager
    当前事务提交/回滚后,TransactionSynchronization 回调触发真正的 invoke()
    
  3. 阶段可选:
    • AFTER_COMMIT(默认)
    • AFTER_ROLLBACK
    • AFTER_COMPLETION(无论提交或回滚)
    • BEFORE_COMMIT

四、一张图总结(文字版)

启动期
  EventListenerMethodProcessor
    扫描所有 Bean → @EventListener 方法
      包装成 ApplicationListenerMethodAdapter
        注册到 AbstractApplicationContext.listeners 列表

运行期
  publishEvent()
    multicastEvent()
      for each ApplicationListener
        类型过滤 → 条件过滤 → 反射 invoke()
          ├─ 同步:直接调用
          ├─ 异步:线程池
          └─ 事务:挂到 TransactionSynchronization

五、回答最原始的问题

“实现原理”可以浓缩成两句话:

  1. 启动期 Spring 通过 EventListenerMethodProcessor 把所有 @EventListener 方法反射包装成 ApplicationListener 对象,注册到容器。
  2. 运行期 每次 publishEvent() 时,Spring 遍历这些监听器,先按事件类型 + SpEL 条件快速过滤,再反射(或线程池、或事务同步)调用目标方法,完成“发布-订阅”。

这就是 @EventListener 零接口、零配置却能生效的全部底层秘密。


原文地址:https://blog.csdn.net/zzz19920821/article/details/155743062

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!