《Spring系列》第18章 监听器Listener

news2024/9/21 0:50:19

前言

陆续阅读了Spring Boot源码中,有不少地方都用到了监听器。每次看到监听器的代码都一头雾水,不懂其中的设计理念,这次决定肝一篇监听器的博文。

一、监听器

1.概述

何为监听器?就是用来监听程序执行的。监听器可以做什么事?可以监听程序执行,使程序在不同的阶段做不同的事情,并且与程序主业务逻辑解耦。

那么读者读完是否很迷茫?如果学过MQ的可以把想象成一种订阅-发布模式,监听器首先订阅事件,然后其它功能主动发布事件,已触发监听器订阅的事件。

举个例子:最常见的购物逻辑中,下订单以后就要扣库存,可以理解为扣库存这个操作监听了订单,这就是一种监听器,当下了订单,此时触发监听器,执行扣库存操作。有没有觉得监听器更像是订阅-发布模式

可能读完这个例子,大家觉得很难理解,这种下订单、扣库存,我在代码里面按照逻辑依次写下去不就完了,为啥要整的这么麻烦?

个人理解:Spring提供的监听器,其实是一种设计模式,目的是为了更好的扩展性,例如像上面的下订单扣库存,肯定代码直接写最省事,但这样就耦合度高了,其它的业务功能都这样写嘛?如果Spring按照这种方式写,那么想扩展功能就只剩改源码了

所以从整体设计的角度,Spring框架为了提供更好的扩展性,采用了观察者模式,设计出了这么一套监听器,以供使用。这样开发人员不仅可以扩展Spring的监听器,也可以自定义新的监听器,一举两得。这样得以让代码更加优雅,便于扩展
在这里插入图片描述

2.组成

Spring的监听器由以下3部分组成:

  1. 事件(ApplicationEvent):要广播,发送的消息. 监听器监听的事情
  2. 监听器(ApplicationListener): 观察者模式中的观察者, 监听器监听特定事件, 并在内部定义了事件发生后的相应逻辑.
  3. 事件发布器(ApplicationEventMulticaster):对应于观察者模式中的被观察者/主题.负责通知观察者. 对外提供发布事件和增删事件监听器的接口.维护事件和事件监听器之间的关系.并在事件发生时负责通知事件监听器.

可能看到这3个词觉得很难理解,那么换一种解释:

  1. 事件:负责调用逻辑,对应订阅-发布中的订阅
  2. 监听器:负责执行逻辑,也就是你的自定义逻辑
  3. 事件发布器:负责根据事件类型去调用你的自定义逻辑,底层就是一个循环,根据你传过来的事件类型,遍历调用对应的监听器

3.Hello World

上面介绍到的例子:下单后减库存. 减库存就是一个事件, 这个事件需要一个事件播放器, 将事件播放出去. 然后另一端事件监听器, 接收到信息,进行处理。简单写一下

第1步:创建一个订单实体类】

@Data
public class Order {

    private String id;

    private String name;

}

第2步:创建一个事件,继承ApplicationEvent

// 继承ApplicationEvent必须重写构造方法
public class OrderEvent extends ApplicationEvent {

    // 一般在调用的时候会把this传进来,也就是当前类
    public OrderEvent(Object source) {
        super(source);
    }

    // 可以在this的基础上,继续增加所需的参数
    public OrderEvent(Object source, Order order) {
        super(source);
	    System.out.println("创建了事件-----");
    }

}

第3步:创建监听器,实现ApplicationListener

@Component
public class OrderEventListener implements ApplicationListener<OrderEvent> {

    @Override
    public void onApplicationEvent(OrderEvent event) {
        // 参数event可以接收传过来的参数,一般就是上一步逻辑处理的结果,也可以不传
        System.out.println("触发了事件----------");
    }

}

第4步:启动类】

@Configuration
@ComponentScan
public class TestMain {

    @Test
    public void m1() {
        // 1.创建IOC容器
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(TestMain.class);

        // 2.创建订单
        Order order = new Order();
        order.setId("订单号:i4u1212412312");
        order.setName("商品名称:T恤");
        System.out.println("订单创建完毕---" + order);

        // 3.发布事件, 这里会借助事件分发器去调用监听器
        ctx.publishEvent(new OrderEvent(this, order));
        System.out.println("结束了-------");
    }

}

写完以后,仔细思考一下,就会发现其实就是A调用B,但是Spring把A叫做事件,B叫做监听器,然后调用由事件分发器执行,解耦合

4.分类

上面简单介绍了监听器,那么监听器有哪些呢?两种类型:一种是内置监听器, 一种是自定义监听器

1) 内置监听器

Spring定义了一个内置监听器的父类ApplicationContextEvent,实现该类的就是内置监听器

在这里插入图片描述

一共有4类实现了ApplicationContextEvent

Event说明
ContextRefreshEvent当容器被实例化或者refresh时发布.如调用refresh()方法. 此处的实例化是指所有的bean都已被加载,后置处理器都被激活,所有单例bean都已被实例化,所有的容器对象都已经准备好可使用. 如果容器支持热重载,则refresh()可以被触发多次(XmlWebApplicationContext支持热刷新, 而GenericApplicationContext不支持热刷新)
ContextStartedEvent当容器启动时发布, 即调用start()方法, 已启用意味着所有的lifecycle都已显示收到了start的信号
ContextStoppedEvent当容器停止时发布. 即调用stop()方法, 既所有的lifecycle bean都已显示接收了stop信号, 关闭的容器可以通过start()方法重启
ContextClosedEvent当容器关闭时发布. 即调用close()方法, 关闭意味着所有的单例bean都已被销毁. 关闭的容器不能被重启或refresh()

总结:这些内置监听器事件是Spring框架自己使用的,以供扩展Spring源码可以使用,例如:我想在Spring初始化完成以后执行某个操作,可以自定义监听器类,监听ContextRefreshEvent事件,然后执行自定义逻辑

在简单点说,这些是Spring框架初始化默认发布的事件,无法更改事件,但你可以监听执行自定义逻辑,当然也可以自定义事件

举个例子】可以直接创建一个自定义监听器,监听ContextRefreshedEvent事件,这样当Spring容器初始化完成,就会执行这个监听器的逻辑

@Component
public class ContextRefreshedEventListener implements ApplicationListener<ContextRefreshedEvent> {

    private String name = "qqweqwe123";

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("所有的bean初始化完成了");
    }
}

2) 自定义监听器

自定义监听器,其实上面的例子已经写过1遍了,那么这里介绍一下实现的2种方式:

方式1:基于接口】

@Component
public class HelloEventListener implements ApplicationListener<OrderEvent> {

  @Override
  public void onApplicationEvent(OrderEvent event) {
      if (event.getName().equals("减库存")) {
        System.out.println("减库存....");
      }
  }
}

方式2:基于注解】

@Component
public class OrderEventListener {

  @EventListener(OrderEvent.class)
  public void onApplicationEvent(OrderEvent event) {
    if (event.getName().equals("减库存")) {
      System.out.println("减库存....");
    }
  }
}

二、源码分析

1.初始化

监听器的初始化,都是在refresh()中,那么下面就来看一下,跟监听器有关的类:
在这里插入图片描述

1) initApplicationEventMulticaster()

初始化一个监听器多播器,默认是SimpleApplicationEventMulticaster,它负责将事件分发到监听器

protected void initApplicationEventMulticaster() {
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
      this.applicationEventMulticaster =
            beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
      if (logger.isTraceEnabled()) {
         logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
      }
   }
   else {
      this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
      beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
      if (logger.isTraceEnabled()) {
         logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
               "[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
      }
   }
}

2) registerListeners()

注册监听器,来源分为3步:

  1. 静态指定的。也就是META-INF/spring.factories中配置的
  2. 容器中获取。包括:引入其它依赖携带的监听器 + 自定义开发的监听器
  3. 把早期的监听器给注入进来,很少用到
protected void registerListeners() {
   // Register statically specified listeners first.
   for (ApplicationListener<?> listener : getApplicationListeners()) {
      getApplicationEventMulticaster().addApplicationListener(listener);
   }

   // Do not initialize FactoryBeans here: We need to leave all regular beans
   // uninitialized to let post-processors apply to them!
   String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
   for (String listenerBeanName : listenerBeanNames) {
      getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
   }

   // Publish early application events now that we finally have a multicaster...
   Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
   this.earlyApplicationEvents = null;
   if (earlyEventsToProcess != null) {
      for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
         getApplicationEventMulticaster().multicastEvent(earlyEvent);
      }
   }
}

3) finishRefresh()

protected void finishRefresh() {
   // Clear context-level resource caches (such as ASM metadata from scanning).
   clearResourceCaches();

   // Initialize lifecycle processor for this context.
   initLifecycleProcessor();

   // Propagate refresh to lifecycle processor first.
   getLifecycleProcessor().onRefresh();

   // Publish the final event.
   // 发布一个事件
   publishEvent(new ContextRefreshedEvent(this));

   // Participate in LiveBeansView MBean, if active.
   LiveBeansView.registerApplicationContext(this);
}

4) 总结

经过以上3步,首先事件发布器有了,默认SimpleApplicationEventMulticaster,监听器有了,已经注入进去了,剩下的就是通过事件发布器,来触发监听器

2.发布

初始化完毕以后,接下来就是使用了,当发布事件的时候,如何执行呢?

先说原理:通过publishEvent()发布事件以后,底层就会根据事件类型,循环判断所有的监听器,找到符合类型的,然后执行。

1) publishEvent() 入口

事件发布,然后交由时间发布器执行

// AbstractApplicationContext.java


@Override
public void publishEvent(ApplicationEvent event) {
   publishEvent(event, null);
}

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
   Assert.notNull(event, "Event must not be null");

   // Decorate event as an ApplicationEvent if necessary
   ApplicationEvent applicationEvent;
   if (event instanceof ApplicationEvent) {
      applicationEvent = (ApplicationEvent) event;
   }
   else {
      applicationEvent = new PayloadApplicationEvent<>(this, event);
      if (eventType == null) {
         eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
      }
   }

   // 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);
   }

   // Publish event via parent context as well...
   if (this.parent != null) {
      if (this.parent instanceof AbstractApplicationContext) {
         ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
      }
      else {
         this.parent.publishEvent(event);
      }
   }
}

2) 发布事件

获取到匹配的监听器,然后循环执行

// SimpleApplicationEventMulticaster.java

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
   ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
   Executor executor = getTaskExecutor();
   // 【获取到匹配的监听器,然后遍历】
   for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
      if (executor != null) {
         executor.execute(() -> invokeListener(listener, event));
      }
      else {
         // 【执行监听器】
         invokeListener(listener, event);
      }
   }
}

3) 获取匹配的监听器

从全部的监听器中,遍历循环,找到支持该事件的监听器,这里代码只展示了大概,具体没有展开,大体就是一个for循环+if判断

protected Collection<ApplicationListener<?>> getApplicationListeners(
      ApplicationEvent event, ResolvableType eventType) {

   Object source = event.getSource();
   Class<?> sourceType = (source != null ? source.getClass() : null);
   ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);

   // Quick check for existing entry on ConcurrentHashMap...
   ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
   if (retriever != null) {
      return retriever.getApplicationListeners();
   }

   if (this.beanClassLoader == null ||
         (ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
               (sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
      // Fully synchronized building and caching of a ListenerRetriever
      synchronized (this.retrievalMutex) {
         retriever = this.retrieverCache.get(cacheKey);
         if (retriever != null) {
            return retriever.getApplicationListeners();
         }
         retriever = new ListenerRetriever(true);
         // 【这里会循环所有的监听器,根据 supportsEventType() 来判断是否支持该事件类型】
         Collection<ApplicationListener<?>> listeners =
               retrieveApplicationListeners(eventType, sourceType, retriever);
         this.retrieverCache.put(cacheKey, retriever);
         return listeners;
      }
   }
   else {
      // No ListenerRetriever caching -> no synchronization necessary
      return retrieveApplicationListeners(eventType, sourceType, null);
   }
}

4) 执行监听器

接下来就是执行监听器了,看到invoke就知道了,反射调用对应的方法即可

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
   ErrorHandler errorHandler = getErrorHandler();
   if (errorHandler != null) {
      try {
         doInvokeListener(listener, event);
      }
      catch (Throwable err) {
         errorHandler.handleError(err);
      }
   }
   else {
      // 【执行】
      doInvokeListener(listener, event);
   }
}

接下来就是执行监听器了,看到invoke就知道了,反射调用对应的方法即可

protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
   ErrorHandler errorHandler = getErrorHandler();
   if (errorHandler != null) {
      try {
         doInvokeListener(listener, event);
      }
      catch (Throwable err) {
         errorHandler.handleError(err);
      }
   }
   else {
      // 【执行】
      doInvokeListener(listener, event);
   }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/736515.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

青岛大学_王卓老师【数据结构与算法】Week05_03_队列的定义和特点_学习笔记

本文是个人学习笔记&#xff0c;素材来自青岛大学王卓老师的教学视频。 一方面用于学习记录与分享&#xff0c; 另一方面是想让更多的人看到这么好的《数据结构与算法》的学习视频。 如有侵权&#xff0c;请留言作删文处理。 课程视频链接&#xff1a; 数据结构与算法基础…

Vue3通透教程【十七】Vite构建TS版本Vue项目

文章目录 &#x1f31f; 写在前面&#x1f31f; 创建TS版本的Vue3项目&#x1f31f; 插件安装&#x1f31f; 写在最后 &#x1f31f; 写在前面 专栏介绍&#xff1a; 凉哥作为 Vue 的忠实 粉丝输出过大量的 Vue 文章&#xff0c;应粉丝要求开始更新 Vue3 的相关技术文章&#…

DocFastSearchTool(文档快速搜索工具)开发日志

目录 项目介绍 项目调研背景 项目需求分析 开发环境 项目涉及基础知识点 项目设计 设计基础 项目框架or架构 项目框架的搭建 系统工具模块--遍历目录 SQLite数据库 在Windows上安装SQLite SQLite命令 SQLite-C/C的API 安装SQLite源码 数据库操作的重要接口 连接…

PB从入坑到放弃(三)数据窗口

PB从入坑到放弃&#xff08;三&#xff09;数据窗口 写在前面一、 数据窗口画板1.1 Design 视窗1.1.1 General tab页设置1.1.2 Generation tab页设置1.1.3 Prefixes tab页设置 1.2 Preview 视窗1.2.1 查找数据1.2.2 翻页1.2.3 增加、删除数据 1.3 Control List 视窗1.4 Data 视…

学生成绩分析项目

数据采集 导入必要的库 import pandas as pd import matplotlib.pyplot as plt import seaborn as sns加载数据集 df pd.read_csv(D:\\桌面\\数据\\student_marks.csv)显示数据框的前几行 # 显示数据框的形状 print("Shape of the dataframe:", df.shape)#显示…

pycharm最新版默认菜单栏等工具不见了

原因 pycharm2022.3新版本&#xff0c;默认使用新UI&#xff08;如下图&#xff09;&#xff0c;这让很多小伙伴来说不太适应&#xff0c;我还是习惯旧版本的界面。在网上搜了许多恢复旧版本界面的教程&#xff0c;说的解决方案都是由于“手贱把菜单栏给隐藏了”&#xff0c;这…

C语言“教父“

在中国&#xff0c;"C语言教父"一般指的是C语言网的创始人黄老师。他通过C语言网为广大学习者提供了丰富的学习资源和知识分享平台&#xff0c;包括大量的C语言教程、C语言题目以及相应的文档、资源等等&#xff0c;为C语言的推广、教学做出了巨大的贡献和推动作用

Go自带库的使用说明

Go 中的时间操作 Golang中与时间有关的操作&#xff0c;主要涉及到 time 包&#xff0c;核心数据结构是 time.Time&#xff0c;如下&#xff1a; type Time struct {wall uint64ext int64loc *Location }1、获取时间相关函数 1.1 获取当前时间 // 返回当前时间&#xff0c…

腾讯云TRTC服务实现小程序语音/视屏会议

腾讯云TRTC服务的入门 TRTC 是腾讯云主打全平台互通的多人音视频通话和低延时互动直播解决方案。TRTC服务有多种客户端的支持&#xff0c;对于IOS、Android、React native等都支持的比较好&#xff0c;我们主要在于 IOS、Android、Web三端进行处理&#xff0c;其中 TRTC Web S…

HarmonyOS学习路之开发篇—流转

流转概述 介绍 随着全场景多设备生活方式的不断深入&#xff0c;用户拥有的设备越来越多&#xff0c;每个设备都能在适合的场景下提供良好的体验&#xff0c;例如&#xff1a;手表可以提供及时的信息查看能力&#xff0c;电视可以带来沉浸的观影体验。但是&#xff0c;每个设备…

网络变压器常见封装

1、单口千兆&#xff08;Single Port&#xff09; 封装类型常见型号Dimension SMD24_1 PIN Pitch&#xff1a;1.00 Length&#xff1a;15.1 Width&#xff1a;7.1/10.0 Height&#xff1a;6.00 JWD&#xff1a; SG24002G Group-tek&#xff1a; HST-24015SR SMD24_2 PIN…

pyspark

连接命令&#xff1a; pyspark --master spark://node1:7077 一个application 大任务可以分解成 多个小任务 jobs&#xff0c; 一个job又可以分解成多个 stages 阶段&#xff0c; 一个stage又可以分解成 多个tasks&#xff08;可以认为是两个线程&#xff09; standalone Zo…

使用Gradio库创建交互式滑块组件

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

QT DAY3

完善文本编辑器 1.mainwindow.h文件 #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QDebug> #include <QIcon> #include <QLabel> #include <QLineEdit> #include <QPushButton> #include <QMessageBo…

如何用Stable Diffusion模型生成个人专属创意名片?

目录 1 什么是二维码&#xff1f;2 什么是扩散模型&#xff1f;3 Stable Diffusion环境搭建4 开始制作创意名片结语 1 什么是二维码&#xff1f; 二维码是一种用于存储和传输信息的方便而广泛使用的图像编码技术。它是由黑色方块和白色空白区域组成的二维图形&#xff0c;可以…

光镊背后的电磁理论 | 涡旋电磁波在无线通信系统中的应用

2018年诺贝尔物理学奖于北京时间10月2日17点50分正式揭晓&#xff0c;发明光镊技术的美国物理学家&#xff0c;阿瑟阿什金&#xff08;Arthur Ashkin&#xff09;&#xff0c;以及开创了啁啾脉冲放大技术的唐娜斯特里克兰&#xff08;Donna Strickland&#xff09;、 热拉尔穆鲁…

持之以恒,安之有度 | 持安科技2周年!

新征程 新未来 持安的同学们已经一起走进 第三个年头啦 近日&#xff0c;持安 北京 上海 深圳 所有公司成员齐聚一堂 共 同 庆 祝 持安科技 成立2周年 持安一体化零信任平台 &#xff0c;引领应用层零信任落地新局面 2021年&#xff0c;何艺&#xff08;持安创始人兼CE…

经典轻量级神经网络(3)ShuffleNet V1及其在Fashion-MNIST数据集上的应用

经典轻量级神经网络(3)ShuffleNet V1及其在Fashion-MNIST数据集上的应用 1 ShuffleNet V1的简述 ShuffleNet 提出了 1x1分组卷积通道混洗 的策略&#xff0c;在保证准确率的同时大幅降低计算成本。 ShuffleNet 专为计算能力有限的设备&#xff08;如&#xff1a;10~150MFLOP…

开发uniapp苹果app,苹果签名证书的创建方法

在uniapp云打包界面&#xff0c;打包苹果app&#xff0c;需要私钥证书p12文件&#xff0c;还需要证书profile文件和证书密码。 这两个文件到底是从什么地方获取的呢&#xff1f;答案是这两个证书需要在苹果开发者中心生成&#xff0c;下面我们这篇教程&#xff0c;将教会大家如…

Java小白的学习之路——day12

目录 一、final 什么是final&#xff1f; 二、接口概述 什么是接口&#xff1f; 与抽象类的区别 常量接口 接口传参多态 四、内部类 什么是内部类&#xff1f; 成员内部类 静态内部类 局部内部类 一、final 什么是final&#xff1f; final从字面意思来看时最终的&a…