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

前言

陆续阅读了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> {@Overridepublic void onApplicationEvent(OrderEvent event) {// 参数event可以接收传过来的参数,一般就是上一步逻辑处理的结果,也可以不传System.out.println("触发了事件----------");}}

第4步:启动类】

@Configuration
@ComponentScan
public class TestMain {@Testpublic 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";@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {System.out.println("所有的bean初始化完成了");}
}

2) 自定义监听器

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

方式1:基于接口】

@Component
public class HelloEventListener implements ApplicationListener<OrderEvent> {@Overridepublic 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 necessaryApplicationEvent 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 initializedif (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 ListenerRetrieversynchronized (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 necessaryreturn 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.hqwc.cn/news/15901.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从单体到SpringBoot/SpringCloud微服务架构无感升级的最佳实践

目录导读 从单体到SpringBoot/SpringCloud微服务架构无感升级的最佳实践1. 业务背景2. 当前问题3. 升级方案3.1 架构设计4. 详细设计4.1 迁移阻碍4.2 解决思路 5. 实现过程5.1 认证兼容改造5.2 抽象业务流程5.2.1 抽象业务的思路5.2.2 抽象业务的抽象编码5.2.3 抽象业务的具体实…

华为VRP系统基础

系列文章目录 华为数通学习&#xff08;1&#xff09; 目录 一&#xff0c;什么是VRP? 二&#xff0c;VRP的发展 三&#xff0c;VRP的文件系统 3.1&#xff0c;系统文件:.cc结尾 ​编辑 3.2&#xff0c;配置文件&#xff1a;.cfg&#xff0c;.zip&#xff0c;.dat结尾 3.…

Redis 删除 key用 del 和 unlink 有啥区别?

问题 del 和 unlink 有啥区别啊&#xff1f;为什么String类型删除不会做异步删除&#xff1f; 彬彬回答 DEL 和 UNLINK 都是同步的释放 key 对象&#xff0c;区别是怎么释放后面的 value 对象 DEL 每次都是同步释放 value 部分&#xff0c;如果 value 很大&#xff0c;例如一…

来啦!OceanBase 第7期技术征文活动获奖名单公布!

“小鱼”的诞生与成长离不开广大开发者的陪伴与支持&#xff0c;我们非常兴奋能把 4.1 版本的这一系列新能力带给大家&#xff0c;“小鱼”会游得更快更远&#xff0c;也会陪伴更多数据库开发者一同成长。 OceanBase 联合墨天轮技术社区&#xff0c;举行「4.1 上手体验」第五届…

学习系统编程No.26【信号处理实战】

引言&#xff1a; 北京时间&#xff1a;2023/6/26/13:35&#xff0c;昨天12点左右睡觉&#xff0c;本以为能和在学校一样&#xff0c;7点左右起床&#xff0c;设置了7点到8点30时间段内的4个闹钟&#xff0c;可惜没想到啊&#xff0c;没醒&#xff0c;直接睡到了12点&#xff…

【复习30-35题】【每天40分钟,我们一起用50天刷完 (剑指Offer)】第二十一天 21/50

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

Paddle OCR 安装使用教程

文章目录 一、简介二、使用教程三、模型调用四、效果展示 一、简介 PaddleOCR是飞浆开源文字识别模型&#xff0c;最新开源的超轻量PP-OCRv3模型大小仅为16.2M。同时支持中英文识别&#xff1b;支持倾斜、竖排等多种方向文字识别&#xff1b;支持GPU、CPU预测&#xff0c;并且…

阿里内部《Java工程师面试手册》火了,完整版 PDF 开放下载

前言 2023金九银十即将来临&#xff0c;很多同学会问Java面试八股文有必要背吗&#xff1f; 我的回答是&#xff1a;很有必要。你可以讨厌这种模式&#xff0c;但你一定要去背&#xff0c;因为不背你就进不了大厂。 国内的互联网面试&#xff0c;恐怕是现存的、最接近科举考…

el-input输入框type=“number“时,禁止鼠标上下滑动改变数值

el-input输入框type"number"时&#xff0c;禁止鼠标上下滑动改变数值 解决方法&#xff1a;在el-input中添加属性设置 mousewheel.native.prevent

深度学习实例分割篇——Mask RCNN原理详解篇

&#x1f34a;作者简介&#xff1a;秃头小苏&#xff0c;致力于用最通俗的语言描述问题 &#x1f34a;专栏推荐&#xff1a;深度学习网络原理与实战 &#x1f34a;近期目标&#xff1a;写好专栏的每一篇文章 &#x1f34a;支持小苏&#xff1a;点赞&#x1f44d;&#x1f3fc;、…

Rust 第三天---内存管理与所有权

前面介绍了环境配置以及基础语法,掌握之后已经可以开始用Rust编写一些简单的程序了,今天就要来介绍一下Rust核心的功能—内存管理与所有权 1. 关于内存管理 无论什么高级语言必须考虑到的一点就是编写程序时对于内存的管理问题,更简单一点解释,利用编程语言能快速高效的分配内…

【零基础入门学习Python---Python中机器学习和人工智能之快速入门实践】

&#x1f680; 零基础入门学习Python&#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜…