Spring事件监听原理

标签: spring 事件 监听 | 发表时间:2023-10-11 17:33 | 作者:prefixKey
出处:https://juejin.cn/backend

Spring 事件监听原理

基于 SpringBoot-3.1.2

Spring 事件机制主要用于业务编码的解耦,例如用户订单办理成功,需要发送短信通知,这是两个不同的业务逻辑,不应该耦合在一起,针对于此,就可以通过事件机制来解决,以下是一个最简单的Spring事件使用示例

定义事件对象

  import lombok.Data;

@Data
public class EventObj {
    private String name;
    public EventObj(String name) {
        this.name = name;
    }
}

准备事件监听器(也就是发布事件后,对应的处理者)

  @EventListener
public void listener1(EventObj event) {
    log.info(event.getName());
}

注入事件发布器(由 Spring 提供)并使用事件发布器进行事件的发布

  // 注入事件发布器
private final ApplicationEventPublisher publisher;

// 发布事件
publisher.publishEvent(new EventObj("普通事件"));

单纯根据 Spring 事件 API 的调用形式来看,采用了经典的观察者模式来实现, publisher 对象作为发布者,被 @EventListener注解标识的方法作为具体的观察者。

那么 Spring 的事件是怎样发布的,又是如何只通过一个简单的 @EventListener注解就将对应的方法注册为了观察者,事件监听方法又是如何与对应的事件对象匹配,这需要探究其中源码的具体实现

事件发布调用流程

Spring 的源码繁多,想要准确的找到某个功能在源码层面中的具体位置也许并不简单,但我们可以根据功能的使用情况,在应用层面打上断点,比如这里的事件发布,我们可以在 listener1方法接收事件对象的地方打上断点,再通过调用堆栈查看源码到底是怎样的调用流程,如下

  @EventListener
public void listener1(EventObj event) {
// 将此行代码断点
    log.info(event.getName());
}

启动项目,发布事件,我们会得到如下调用堆栈

其中 sendEvent是我们业务方法发布事件的地方,再往下,进入了 Spring 的事件发布方法

  /**
 * Publish the given event to all listeners.
 * <p>Note: Listeners get initialized after the MessageSource, to be able
 * to access it within listener implementations. Thus, MessageSource
 * implementations cannot publish events.
 * @param event the event to publish (may be an {@link ApplicationEvent}
 * or a payload object to be turned into a {@link PayloadApplicationEvent})
 */
@Override
public void publishEvent(Object event) {
    publishEvent(event, null);
}

继续进入下一步的 publishEvent(event, null);逻辑,有如下关键源码

  ......
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
    this.earlyApplicationEvents.add(applicationEvent);
}
else {
    // 实际调用
    getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
......

到这里,我们可以猜测,通过 getApplicationEventMulticaster()获取了一个事件发布器,然后调用 multicastEvent()将事件发布出去,即找到对应的事件监听方法,将事件源交给其处理。

事实也是如此, multicastEvent(applicationEvent, eventType)方法的源码如下

  @Override
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event));
    Executor executor = getTaskExecutor();
    // 获取这个事件对应的监听器集合,也就是被 @EventListener 注解标识的事件监听器
    // 这也说明,同一个事件对象可以被多个 @EventListener 注解标识的事件监听方法所处理
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        // 是否使用了线程池(异步方法)
        if (executor != null) {
            // 使用线程池来异步执行事件处理逻辑
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            // 否则同步调用事件处理逻辑
            invokeListener(listener, event);
        }
    }
}

继续跟踪源码到 doInvoke()方法中,部分源码如下

  protected Object doInvoke(Object... args) {
    Object bean = getTargetBean();
    // Detect package-protected NullBean instance through equals(null) check
    if (bean.equals(null)) {
        return null;
    }

    ReflectionUtils.makeAccessible(this.method);
    try {
        // 调用目标方法,即对应的事件处理方法
        return this.method.invoke(bean, args);
    }
    ......

事件监听关系绑定

通过上面的分析,整个事件从发布到调用的流程已经大致结束了,但仍然存在一些疑问,在目前的源码中,并没有看到 Spring 是怎样将事件对象和具体的事件监听方法进行绑定的。

这说明这种关联关系并不是在调用时的时候建立的,而根据 Spring 的一贯套路,这种关系极有可能在容器启动刷新的时候,通过一些后处理器增强来实现,想要观察到这里面的源码,我们需要从上面的 doInvoke()方法中查找蛛丝马迹,可以看到,目标方法是通过 this.method得到和调用的,这里就可以确定,Sping 将事件对象和对应事件监听方法的关系绑定到了 doInvoke()方法所属的类 org.springframework.context.event.ApplicationListenerMethodAdapter

那么只需要将断点打在 ApplicationListenerMethodAdapter的构造方法,就可以探究这种绑定关系是怎样建立的,由此我们可以得到如下调用栈

可以看到,从 main 方法,一直到容器刷新的 finishBeanFactoryInitialization(beanFactory)调用处,这是 Spring 实例和初始化所有非懒加载单例 bean 的地方,继续跟进,在 finishBeanFactoryInitialization(beanFactory)方法的最后,真正进行 bean 的实例和初始化

  // Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();

preInstantiateSingletons()方法的源码如下

  @Override
public void preInstantiateSingletons() throws BeansException {
    if (logger.isTraceEnabled()) {
        logger.trace("Pre-instantiating singletons in " + this);
    }

    // Iterate over a copy to allow for init methods which in turn register new bean definitions.
    // While this may not be part of the regular factory bootstrap, it does otherwise work fine.
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

    // Trigger initialization of all non-lazy singleton beans...
    for (String beanName : beanNames) {
        RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
        if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
            if (isFactoryBean(beanName)) {
                Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
                if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
                    getBean(beanName);
                }
            }
            else {
                getBean(beanName);
            }
        }
    }

    // Trigger post-initialization callback for all applicable beans...
    for (String beanName : beanNames) {
        Object singletonInstance = getSingleton(beanName);
        // 实现了 SmartInitializingSingleton 接口的 Bean, 会执行初始化完成之后的生命周期
        // 此时容器中的所有 Bean 已经完成了实例和初始化, 这里的初始化主要用于处理多个 Bean 之间的依赖关系
        if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {
            StartupStep smartInitialize = getApplicationStartup().start("spring.beans.smart-initialize")
                    .tag("beanName", beanName);
            // 执行 Bean 的 afterSingletonsInstantiated() 初始化生命周期
            smartSingleton.afterSingletonsInstantiated();
            smartInitialize.end();
        }
    }
}

通过 debug 的调用堆栈可以看到, ApplicationListenerMethodAdapter的创建,正是由 beanNameorg.springframework.context.event.internalEventListenerProcessorbean 中的 afterSingletonsInstantiated()调用触发,如下图

而通过 internalEventListenerProcessor这个 beanName实际获取到的 bean 为 org.springframework.context.event.EventListenerMethodProcessor,其定义如下

  public class EventListenerMethodProcessor
    implements SmartInitializingSingleton, ApplicationContextAware, BeanFactoryPostProcessor {
    ......

可以看到,它实现了 SmartInitializingSingletonBeanFactoryPostProcessor接口,说明其中会包含一些增强逻辑,但事件监听关系并不在这里绑定,这里之所以实现 BeanFactoryPostProcessor接口,是为了在 Spring 容器刷新时,通过 postProcessBeanFactory()方法执行一些初始化(后面会说明)

真正处理 Spring 事件相关的逻辑,在 afterSingletonsInstantiated方法中定义。

继续跟踪堆栈, afterSingletonsInstantiated()方法源码如下

  @Override
public void afterSingletonsInstantiated() {
    ConfigurableListableBeanFactory beanFactory = this.beanFactory;
    Assert.state(this.beanFactory != null, "No ConfigurableListableBeanFactory set");
    String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
    // 遍历每个 bean, 然后调用 processBean(beanName, type) 对其执行 Spring 事件相关的增强逻辑
    for (String beanName : beanNames) {
        if (!ScopedProxyUtils.isScopedTarget(beanName)) {
            Class<?> type = null;
            try {
                type = AutoProxyUtils.determineTargetClass(beanFactory, beanName);
            }
            catch (Throwable ex) {
                // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                if (logger.isDebugEnabled()) {
                    logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
                }
            }
            if (type != null) {
                if (ScopedObject.class.isAssignableFrom(type)) {
                    try {
                        Class<?> targetClass = AutoProxyUtils.determineTargetClass(
                                beanFactory, ScopedProxyUtils.getTargetBeanName(beanName));
                        if (targetClass != null) {
                            type = targetClass;
                        }
                    }
                    catch (Throwable ex) {
                        // An invalid scoped proxy arrangement - let's ignore it.
                        if (logger.isDebugEnabled()) {
                            logger.debug("Could not resolve target bean for scoped proxy '" + beanName + "'", ex);
                        }
                    }
                }
                try {
                    processBean(beanName, type);
                }
                catch (Throwable ex) {
                    throw new BeanInitializationException("Failed to process @EventListener " +
                            "annotation on bean with name '" + beanName + "': " + ex.getMessage(), ex);
                }
            }
        }
    }
}

主要流程为

  • 遍历每个 bean
  • 对每个 bean 执行 processBean(beanName, type)方法

processBean(beanName, type)方法源码如下

  private void processBean(final String beanName, final Class<?> targetType) {
    // nonAnnotatedClasses 用于存储未标注 @EventListener 注解的类, 减少不必要的扫描
    /*
    调用 AnnotationUtils.isCandidateClass(targetType, EventListener.class) 检查
    当前 bean 中是否携带了 @EventListener 注解
    */
    if (!this.nonAnnotatedClasses.contains(targetType) &&
            AnnotationUtils.isCandidateClass(targetType, EventListener.class) &&
            !isSpringContainerClass(targetType)) {

        Map<Method, EventListener> annotatedMethods = null;
        try {
            // 查找到当前 bean 中, 标识了 @EventListener 注解的方法
            annotatedMethods = MethodIntrospector.selectMethods(targetType,
                    (MethodIntrospector.MetadataLookup<EventListener>) method ->
                            AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
        }
        catch (Throwable ex) {
            // An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
            if (logger.isDebugEnabled()) {
                logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
            }
        }

        // 如果当前类中没有标识了 @EventListener 注解的方法, 说明这不是一个监听器类, 将其加入 @EventListener 集合
        // 避免重复扫描
        if (CollectionUtils.isEmpty(annotatedMethods)) {
            this.EventListener.add(targetType);
            if (logger.isTraceEnabled()) {
                logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
            }
        }
        else {
            // Non-empty set of methods
            ConfigurableApplicationContext context = this.applicationContext;
            Assert.state(context != null, "No ApplicationContext set");
            // 获取事件监听器的创建工厂
            List<EventListenerFactory> factories = this.eventListenerFactories;
            Assert.state(factories != null, "EventListenerFactory List not initialized");
            // 遍历每个事件监听方法
            for (Method method : annotatedMethods.keySet()) {
                // 遍历事件监听器的创建工厂, 看那个工厂支持创建当前事件监听方法对应的事件监听器
                for (EventListenerFactory factory : factories) {
                    if (factory.supportsMethod(method)) {
                        // 获取事件监听方法
                        Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
                        // 创建事件监听器
                        ApplicationListener<?> applicationListener =
                                factory.createApplicationListener(beanName, targetType, methodToUse);
                        if (applicationListener instanceof ApplicationListenerMethodAdapter alma) {
                            alma.init(context, this.evaluator);
                        }
                        // 加入容器
                        context.addApplicationListener(applicationListener);
                        break;
                    }
                }
            }
            if (logger.isDebugEnabled()) {
                logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
                        beanName + "': " + annotatedMethods);
            }
        }
    }
}

主要流程为

  • 检查当前 bean 中是否标识了 @EventListener注解,如果有,则进入后续流程

  • 查找到当前 bean 中所有标识了 @EventListener注解的方法,如果没有,则将当前 bean 放入 nonAnnotatedClasses集合,避免重复扫描

  • 进入事件监听器的创建流程

    • 首先获取事件监听器的创建工厂集合 eventListenerFactories,这个工厂集合是在 Spring 容器刷新时,通过调用 invokeBeanFactoryPostProcessors(),为每个 BeanFactoryPostProcessor调用其 postProcessBeanFactory()方法时被赋值的,集合中默认存在 org.springframework.context.event.DefaultEventListenerFactoryorg.springframework.transaction.event.TransactionalEventListenerFactory两种事件监听器的创建工厂,相关源码如下

            @Override
      public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
          this.beanFactory = beanFactory;
          // 获取 EventListenerFactory 类型的 bean
          Map<String, EventListenerFactory> beans = beanFactory.getBeansOfType(EventListenerFactory.class, false, false);
          List<EventListenerFactory> factories = new ArrayList<>(beans.values());
          AnnotationAwareOrderComparator.sort(factories);
          // 赋值给 EventListenerMethodProcessor 的 eventListenerFactories 属性
          // 后续在 processBean() 流程中会获取到
          this.eventListenerFactories = factories;
      }
      
    • 遍历事件监听方法,通过调用 factory.supportsMethod(method)从事件监听器工厂中查找到支持通过当前事件监听方法创建事件监听器的工厂,这里有一个细节, TransactionalEventListenerFactory工厂的优先级要高于 DefaultEventListenerFactory工厂,因为 TransactionalEventListenerFactory是用于支持 @TransactionalEventListener注解的,相对于 @EventListener注解, @TransactionalEventListener注解的适用范围要小一些,因此先确认 TransactionalEventListenerFactory工厂是否支持当前事件方法

    • 获取当前 bean 中的所有事件监听方法

    • 根据事件监听方法,调用事件监听器创建工厂的 createApplicationListener(beanName, targetType, methodToUse)方法,创建对应的事件监听器,这里会调用到 org.springframework.context.event.ApplicationListenerMethodAdapter的构造器,相关源码如下

            public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
          this.beanName = beanName;
          this.method = BridgeMethodResolver.findBridgedMethod(method);
          // 获取 Spring 事件处理的目标方法, 即被 @EventListener 或 @TransactionalEventListener 注解标识的方法
          this.targetMethod = (!Proxy.isProxyClass(targetClass) ?
                  AopUtils.getMostSpecificMethod(method, targetClass) : this.method);
          this.methodKey = new AnnotatedElementKey(this.targetMethod, targetClass);
      
          EventListener ann = AnnotatedElementUtils.findMergedAnnotation(this.targetMethod, EventListener.class);
         
          // 解析出当前事件方法实际支持的事件对象类型
          this.declaredEventTypes = resolveDeclaredEventTypes(method, ann);
          this.condition = (ann != null ? ann.condition() : null);
          this.order = resolveOrder(this.targetMethod);
          String id = (ann != null ? ann.id() : "");
          this.listenerId = (!id.isEmpty() ? id : null);
      }
      

      在上面的构造函数中,分别解析了事件处理方法以及事件处理方法支持的类型,将二者的关系保存到了 ApplicationListenerMethodAdapter中,至此,【事件监听关系】绑定成功

根据事件绑定关系调用

在前面的内容中,我们将 Spring 事件从声明,到监听器的创建,监听关系的绑定进行了较为完整的分析,下面继续回到 Spring 事件发布调用的流程,详细了解 Sring 是怎么通过事件监听绑定关系调用到事件监听方法的

我们将断点继续打在事件监听方法体中,如下

  @EventListener
public void listener1(EventObj event) {
// 将此行代码断点
    log.info(event.getName());
}

发布事件,得到如下调用堆栈

有了前面的基础,我们可以快速来到 multicastEvent()方法中,源码如下

  @Override
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event));
    Executor executor = getTaskExecutor();
    // 获取这个事件对应的监听器集合,也就是被 @EventListener 注解标识的事件监听器
    // 这也说明,同一个事件对象可以被多个 @EventListener 注解标识的事件监听方法所处理
    for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        // 是否使用了线程池(异步方法)
        if (executor != null) {
            // 使用线程池来异步执行事件处理逻辑
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            // 否则同步调用事件处理逻辑
            invokeListener(listener, event);
        }
    }
}

关键方法为 getApplicationListeners(event, type),通过方法签名可以看出,这是根据事件类型来确定对应的事件监听器

getApplicationListeners(event, type)内部最终会调用到 retrieveApplicationListeners(),其部分源码如下

  // 此方法用于实际检索给定事件和源类型对应的事件监听器
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
        ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {

    List<ApplicationListener<?>> allListeners = new ArrayList<>();
    Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
    Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);

    Set<ApplicationListener<?>> listeners;
    Set<String> listenerBeans;
    synchronized (this.defaultRetriever) {
        listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
        listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
    }

 
    // 添加以编程方式注册的侦听器, 即通过 @EventListener 或 @TransactionalEventListener 注册的事件监听器
    for (ApplicationListener<?> listener : listeners) {
        // 通过 supportsEvent(listener, eventType, sourceType) 方法来确定支持当前事件的监听器
        if (supportsEvent(listener, eventType, sourceType)) {
            if (retriever != null) {
                filteredListeners.add(listener);
            }
            allListeners.add(listener);
        }
    }

supportsEvent(listener, eventType, sourceType)方法源码如下

  @Override
public boolean supportsEventType(ResolvableType eventType) {
    // declaredEventTypes 保存了当前事件监听器支持的事件类型, 这在上面的【事件监听关系绑定】流程中被确定
    for (ResolvableType declaredEventType : this.declaredEventTypes) {
        if (eventType.hasUnresolvableGenerics() ?
                declaredEventType.toClass().isAssignableFrom(eventType.toClass()) :
                declaredEventType.isAssignableFrom(eventType)) {
            return true;
        }
        if (PayloadApplicationEvent.class.isAssignableFrom(eventType.toClass())) {
            if (eventType.hasUnresolvableGenerics()) {
                return true;
            }
            ResolvableType payloadType = eventType.as(PayloadApplicationEvent.class).getGeneric();
            if (declaredEventType.isAssignableFrom(payloadType)) {
                return true;
            }
        }
    }
    return false;
}

上述逻辑,主要是在遍历判断当前监听器支持的事件类型列表中, 是否存在一个与事件类型相同的类型,如果存在,说明当前监听器支持处理当前事件。

最终会将所有支持当前发布时间的监听器都通过上面这种方式查找到并且返回,后续将通过事件监听器中的【事件监听绑定关系】调用到目标方法。

整个事件发布到监听调用的流程也就结束了。

相关 [spring 事件 监听] 推荐:

Spring事件监听原理

- - 掘金 后端
基于 SpringBoot-3.1.2. Spring 事件机制主要用于业务编码的解耦,例如用户订单办理成功,需要发送短信通知,这是两个不同的业务逻辑,不应该耦合在一起,针对于此,就可以通过事件机制来解决,以下是一个最简单的Spring事件使用示例. 准备事件监听器(也就是发布事件后,对应的处理者).

Android 监听锁屏/开屏事件

- - CSDN博客推荐文章
(1) 监听BroadcastReceiver. (2)获取PowerManager事件. Intent.ACTION_SCREEN_ON : 屏幕点亮 Intent.ACTION_SCREEN_OFF :屏幕关闭 Intent.ACTION_USER_PRESENT: 用户解锁. 监听用户解锁需要在AndroidManifest中注册权限.

Spring 框架笔记—4.16.2 标准与自定义事件

- - 企业架构 - ITeye博客
            Spring 框架笔记—4.16.2 标准与自定义事件. If a bean that implements the ApplicationListener interface is deployed into the context, every time an ApplicationEvent gets published to the ApplicationContext, that bean is notified.

兼容各个浏览器版本的事件监听器工具

- - CSDN博客推荐文章
作者:kingwolf_JavaScript 发表于2012-5-15 19:12:15 原文链接. 阅读:6 评论:0 查看评论.

Spring详解

- - CSDN博客架构设计推荐文章
Spring是一个开源的控制反转(Inversion of Control ,IoC)和面向切面(AOP)的容器框架.它的主要目的是简化企业开发.. PersonDaoBean 是在应用内部创建及维护的. 所谓控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的.

Spring定时

- - 行业应用 - ITeye博客
spring的定时任务配置分为三个步骤:. . . . . .

简单Spring+hessian

- - Web前端 - ITeye博客
简单的Spring+hessian. dist\modules里面的 spring-webmvc.jar . lib\caucho 里面的hessian-3.1.3.jar. 里面有个接口interface:. 建立一个model层:(实现Serializable接口). 在WEB-INF下面创建一个remoting-servlet.xml:.

Spring MVC 和 Struts2

- - CSDN博客架构设计推荐文章
Web层面的框架学习了三个Struts1和2,SpringMVC,那他们之间肯定存在一个优劣和适用的环境,Struts1和2的异同点我已经做过对比《 Struts1和Struts2》,这篇将对比下Struts2和SpringMVC的异同,下面数据基本来源于网络,本人是搜集整理所得,供大家参考. 一个项目使用什么样的技术,决定的因素很多,我所能想到的有:对系统的性能、开发的效率、团队学习的成本、业务场景等,下面尽量从这几个方面入手,来分析比较下他们之间存在的优劣.

Spring AOP详解

- - Java - 编程语言 - ITeye博客
        最近项目中遇到了以下几点需求,仔细思考之后,觉得采用AOP来解决. 一方面是为了以更加灵活的方式来解决问题,另一方面是借此机会深入学习Spring AOP相关的内容. 例如,以下需求不用AOP肯定也能解决,至于是否牵强附会,仁者见仁智者见智. 1.对部分函数的调用进行日志记录,用于观察特定问题在运行过程中的函数调用情况.

spring roo 入门

- - 企业架构 - ITeye博客
Spring官网下载STS(如果没有STS). 创建Spring Roo基础项目. 根 据ROO的提示输入jpa setup再按ctrl+space,很遗憾这个快捷键已经被输入法切换占用,不能借助提示输入命令,但我们可以打开ROO命令向导,这里我们输入jpa 可以查到这条命令的用法,根据提示增加provider和database选项来完成命令.