Spring及Springboot事件机制详解

程序设计的所有原则和方法论都是追求一件事——简单——功能简单、依赖简单、修改简单、理解简单。因为只有简单才好用,简单才好维护。因此,不应该以评论艺术品的眼光来评价程序设计是否优秀,程序设计的艺术不在于有多复杂多深沉,而在于能用多简单的方式实现多复杂的业务需求;不在于只有少数人能理解,而在于能让更多人理解。

Spring提供了强大的扩展体系,其中本篇介绍的事件机制是其扩展体系中一个主要分支。本篇先介绍Spring事件机制原理,再总结下spring和spring boot启动过程的主要事件,接着通过示例展示了自定义监听器的4种主要的注册方式,最后通过示例介绍了自定义事件的发布方式。

1. spring事件机制原理

1.1 观察者模式

1.1.1 模式介绍

观察者(Observer)模式: 多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
参与者角色:

  1. Subject: 需要被观察的对象,抽象类
  2. ConcreteSubject:具体的需要被观察的对象
  3. Observer: 观察者接口,定义一个有变更时的触发接口
  4. ConcreteObserver: 具体观察者,实现具体的操作逻辑

观察者模式基本类图如下
在这里插入图片描述

1.1.2 观察者模式设计要点

  1. Observer设计要点
    关于Observer接口方法设计有2种方式:

    1. 以变更类型、事件类型为方法名,如onStateChage()、onNameChange()、onStart()、onClose()等等。这种方式实现简单,但适合变更类型确定的场景。比如spring boot启动事件的监听器SpringApplicationRunListener的方法设计就采用这种。
    2. 以抽象事件类型为参数,如onAbstractXxxEvent(AbstractXxxEvent e)。这种方式扩展性强,适合变更类型不确定的场景,但实现方式复杂。spring的ApplicationContext容器就是采用这种方式实现。
  2. Subject设计要点,单一职责原则,一种类型Observer只处理一种类型Subject,比如Spring中ApplicationContext容器的观察者为ApplicationListener,spring boot的SpringApplication程序的观察者为SpringApplicationRunListener

1.2 spring事件机制

1.2.1 spring事件设计介绍

spring事件的实现是观察者模式的应用,spring事件监听器类图如下,下面同时也总结了spring的事件监听器接口与spring容器、spring boot有关的类图关系
在这里插入图片描述

1.2.2 与观察者模式对比

根据上面类图与观察者类图对比,spring各个类与观察者类图的类映射关系如下:

spring类观察者类/作用
ApplicationListenerObserver,用于监听spring容器状态
ApplicationEventObserver观察ApplicationContext容器状态变更的具体类型,比如容器刷新完成事件ContextRefreshedEvent
ApplicationContextSubject,是ApplicationListener监听的主体对象
ApplicationEventPublisher定义Subject的notify相关的方法, 面向应用代码的事件发布器,应用代码可以使用该接口发布事件
ApplicationEventMulticaster多播器:把Subject发布的事件分发给具体的ApplicationListener监听器
SpringApplicationRunListenerObserver,用于监听springboot应用程序SpringApplication状态,主要实现为EventPublishingRunListener
SpringApplicationSubject,是SpringApplicationRunListener监听的主体对象
SpringApplicationRunListeners多播器:把springboot应用启动事件分发给各个SpringApplicationRunListener监听器对象

1.2.3 多播器ApplicationEventMulticaster

在Spring事件机制的实现中,ApplicationEventMulticaster多播器是核心部分。它也是外观模式的应用,它的功能实现说明如下:

  1. 它把对监听器的管理和通知接口从Subject类中分离,这样完全实现了ApplicationContext与监听器的解耦。
  2. ApplicationEventMulticaster面向接口编程,可以管理任何ApplicationListener对象。
  3. 还可以根据ApplicationEvent事件类型匹配适合的监听器,从而实现监听器之间的独立性。
  4. 客户端应用代码可以直接通过ApplicationEventMulticaster对象发送ApplicationEvent事件。

spring中多播器的实现类为SimpleApplicationEventMulticaster,它分发事件的代码如下:

// SimpleApplicationEventMulticaster源码@Overridepublic void multicastEvent(ApplicationEvent event) {multicastEvent(event, null);}@Overridepublic void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event));Executor executor = getTaskExecutor();for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {if (executor != null && listener.supportsAsyncExecution()) {// 如果设置线程池对象、监听器支持异步执行,则异步执行监听器try {executor.execute(() -> invokeListener(listener, event));} catch (RejectedExecutionException ex) {// Probably on shutdown -> invoke listener locally insteadinvokeListener(listener, event);}} else {// 默认同步执行监听器invokeListener(listener, event);}}}

源码说明有2点:

  1. 执行getApplicationListeners方法会把事件类型与监听器ApplicationListener的泛型类型进行匹配(感兴趣可以翻阅其源码),最终返回所有匹配的监听器。
  2. 默认同步执行各个监听器,如果向多播器设置了执行线程池对象且监听器支持异步执行时才会异步执行监听器。

2. spring boot 启动过程中的事件

以下按启动时触发顺序依次排列说明:

ApplicationEvent实现类Event Source类何时触发
*ApplicationStartingEventSpringApplicationBootstrapRegistry创建完成,SpringApplicationRunListener加载完后触发
*ApplicationEnvironmentPreparedEventSpringApplication环境变量ConfigurableEnvironment加载完成后触发,该事件会触发EnvironmentPostProcessor实现执行
*ApplicationContextInitializedEventSpringApplication执行所有ApplicationContextInitializer的initialize方法完成后触发
*ApplicationPreparedEventSpringApplication基本的BeanDefinition已经被加载,准备刷新容器前触发
ServletWebServerInitializedEventTomcatWebServer所有bean都创建完成后由WebServerStartStopLifecycle生命周期监控对象触发
*ContextRefreshedEventAnnotationConfigServletWebServerApplicationContext所有bean都创建完成后触发
*ApplicationStartedEventSpringApplication容器刷新完成后触发
AvailabilityChangeEventAnnotationConfigServletWebServerApplicationContext与ApplicationStartedEvent事件同级别
*ApplicationReadyEventSpringApplication所有ApplicationRunner和CommandLineRunner扩展执行完后触发
*ContextClosedEventAnnotationConfigServletWebServerApplicationContextspring容器将关闭时触发,在执行Lifecycle#stop方法和bean的desdtroy方法前触发
*ApplicationFailedEventAnnotationConfigServletWebServerApplicationContext任何时候异常就会触发

上面事件ContextRefreshedEvent来自spring-context模块,其它都来自spring-boot模块。其中带*的是关键事件,可以根据需要,监听这些事件来完成你的业务处理,比如:

  1. 手动添加扩展实现,如监听ApplicationEnvironmentPreparedEvent或实现EnvironmentPostProcessor接口,可以添加自己的ApplicationContextInitializer对象
  2. 从配置中心拉取配置,如监听ApplicationContextInitializedEvent,添加自己的配置,如从配置中心获取配置

3. spring boot中事件监听器有几种注册方式

大方向有4种方式:

  1. spring context提供了2种方式:一种是向spring容器注册监听器bean,如@Compnent注解的监听器;另一种是使用@EventListener注解到bean的方法上。
  2. spring boot提供了2种方式:一种是在spring.factories文件注册,另一种是使用SpringApplication对象的addListeners方法手动添加。

spring context提供的方式和spring boot提供的方式有什么不同?

  1. spring context提供的方式注册的监听器不能监听到spring boot应用启动时的事件,比如ApplicationStartingEventApplicationEnvironmentPreparedEvent ,因为spring boot启动时发出的这些事件是在spring容器创建监听器bean之前发生的。
  2. ContextRefreshedEvent事件后两种方式注册监听器都可监听到。因为这个时候spring容器已经完成监听器bean的创建。

下面通过示例分别介绍这4种方式的使用说明。

3.1 spring context提供的方式

3.1.1 实现ApplicationListener接口并注入Spring容器。

可通过@Compnent注解把bean交由spring容器,也可通过其它方式如@Bean注解,如下示例


@Component
public class ApplicationListenerImpl implements ApplicationListener<ApplicationEvent> {@Overridepublic void onApplicationEvent(ApplicationEvent event) {System.out.println("get event: " + event);}
}

3.1.2 通过@EventListener注解到bean的方法上

如下示例


@Component
public class EventListenerByAnnotation {@EventListenerpublic void on(ApplicationEvent event) {EventLogger.write("event_annotation.txt", event);}
}

与上面方式的不同是,上面的方式更先执行,这种@EventListener注解的方式更后执行。因为两种监听器的注册顺序不一样,前者更先,后者更后。

3.2 spring boot提供的方式

3.2.1 通过在META-INF/spring.factories文件中添加ApplicationListener实现类

监听器示例代码如下:

public class ApplicationListenerImpl implements ApplicationListener<ApplicationEvent> {@Overridepublic void onApplicationEvent(ApplicationEvent event) {System.out.println("get event: " + event);}
}

ApplicationListenerImpl无需标注@Component注解,后面只需要在META-INF/spring.factories文件添加如下内容即可:

# 文件:spring.factories
org.springframework.context.ApplicationListener=com.example.ApplicationListenerImpl

这种方式就可以监听spring boot的事件,如上前面表格。

3.2.2 使用SpringApplication对象的addListeners方法手动添加ApplicationListener对象

这种方式和上面方式一样,不同的就是上面是通过配置文件,而该种方式是通过写代码实现,示例代码如下:


@SpringBootApplication
public class MyApplication {public static void main(String[] args) {SpringApplication application = new SpringApplication(MyApplication.class);// 手动添加监听器application.addListeners(new ApplicationListenerImpl());// 启动spring boot应用application.run(args);}
}

4. 自定义事件和监听器

  1. 定义事件和注册监听器,示例代码如下:
// 定义事件
public class MyApplicationEvent extends ApplicationEvent {public MyApplicationEvent(Object source) {super(source);}
}// 定义监听器,并使用@Component注解
@Component
public class MyEventListener implements ApplicationListener<MyApplicationEvent> {@Overridepublic void onApplicationEvent(MyApplicationEvent event) {System.out.println(event);}
}
  1. 选择一个时间点发布事件。比如下面代码是在获得ApplicationContext对象是发布自定义事件。

@Component
public class MyEventPublisher implements ApplicationContextAware {@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {applicationContext.publishEvent(new MyApplicationEvent(applicationContext));}
}

5. 注意事项

  1. spring中的事件默认是同步处理,等所有匹配的监听器都执行完后才会执行后面步骤。详见SimpleApplicationEventMulticaster多播器multicastEvent方法源码。
  2. 在自定义事件中避免出现事件依赖环,否则会出现StackOverflowError异常。即A监听器发布B事件,B监听器发布C事件,C监听器发布A事件。

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

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

相关文章

bigcache源码解析

1. 设计目标 Bigcache 是用 Golang 实现的本地内存缓存的开源库&#xff0c;主打的就是可缓存数据量大&#xff0c;查询速度快。 在其官方的介绍文章《 Writing a very fast cache service with millions of entries in Go 》一文中&#xff0c;明确提出的 bigcache 的设计目标…

“20人+14天”,个人开发者如何通过 Google Play 谷歌封闭测试

个人开发者的应用测试要求 为了帮助开发者提供高品质的应用从而带给用户更优质的使用体验&#xff0c;Google为所有在2023年11月13日之后创建的个人开发者账号增加了一项要求&#xff1a; 至少有20名测试人员在过去至少14天内选择持续参与测试。 满足这项要求后即可申请正式版…

SqlServer: 安装或升级到SqlServer2022

一、下载安装包。 https://info.microsoft.com/ww-landing-sql-server-2022.html?lcidzh-CN 简单注册一下之后&#xff0c;就可以下载安装包了。 或者在我的资源中下载&#xff1a; https://download.csdn.net/download/yenange/89709660 系统要求&#xff1a; https://…

<数据集>遥感航拍飞机和船舶和识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;19973张 标注数量(xml文件个数)&#xff1a;19973 标注数量(txt文件个数)&#xff1a;19973 标注类别数&#xff1a;2 标注类别名称&#xff1a;[ship,plane] 序号类别名称图片数框数1ship17575416292plane239815…

微信小程序webgl 显示图片

// wxml <view class"container"><!-- 加载地图容器 --><canvas type"webgl" id"testMap" style"width: 100%; height: 100%;" disable-scroll bindtouchstart"touchStart" bindtouchmove"touchMove&qu…

二开PHP泛目录生成源码 可生成新闻页面和关键词页面——码山侠

PS 本资源提供给大家学习及参考研究借鉴美工之用&#xff0c;请勿用于商业和非法用途&#xff0c;无任何技术支持&#xff01; 下载i5i.net 泛目录可以用来提升网站收录和排名 合理运用目录可以达到快速出词和出权重的效果 程序小 基本的服务器都带的得动 打开i5i.net——…

HarmonyOS开发实战( Beta5版)不要使用函数/方法作为复用组件的入参规范实践

概述 在滑动场景下&#xff0c;常常会对同一类自定义组件的实例进行频繁的创建与销毁。此时可以考虑通过组件复用减少频繁创建与销毁的能耗。组件复用时&#xff0c;可能存在许多影响组件复用效率的操作&#xff0c;本篇文章将重点介绍如何通过组件复用四板斧提升复用性能。 组…

惠中科技光伏清洗剂:科技创新引领绿色清洁新风尚

惠中科技光伏清洗剂&#xff1a;科技创新引领绿色清洁新风尚 在光伏产业蓬勃发展的今天&#xff0c;光伏板的清洁问题日益凸显&#xff0c;成为影响发电效率的关键因素之一。面对传统清洗方法效率低、成本高、环境影响大等痛点&#xff0c;惠中科技以科技创新为驱动&#xff0…

HIVE 数据仓库工具之第二部分(数据库相关操作)

HIVE 数据仓库工具之第二部分&#xff08;数据库相关操作&#xff09; 一、Hive 对数据库的操作1.1 创建数据库1.1.1 创建数据库语法1.1.3 示例 1.2 使用数据库1.2.1 使用数据库语法1.2.2 示例 1.3 修改数据库1.3.1 修改数据库的语法1.3.2 示例 1.4 删除数据库1.4.1 删除数据库…

【IT工具】Windows下XMind安装教程【不要米】及常用快捷键

目录 下载相关资料安装改造备注附录&#xff1a;Xmind 快捷键 下载相关资料 下载地址&#xff1a;链接: https://pan.baidu.com/s/1aSvhE_U2WKGQ3oaGvcaHqA?pwd6666 提取码: 6666 安装 双击Xmind.exe安装 安装完成之后&#xff0c;不要登录&#xff0c;关闭就行 改造 …

数据结构(单向链表)

单向链表代码 #ifndef _LINK_H_#define _LINK_H_typedef int DataType;typedef struct node {DataType data;struct node *pnext; }Link_Node_t;typedef struct link {Link_Node_t *phead;int clen; }Link_t;extern Link_t *link_creat(); extern int push_link_head(Link_t *…

2018年系统架构师案例分析试题一

目录 案例 【题目】 【问题 1】(8 分) 【问题 2】(8 分) 【问题 3】(9 分) 【答案】 【问题 1】解析 【问题 2】解析 【问题 3】解析 相关推荐 案例 阅读以下关于软件系统设计的叙述&#xff0c;在答题纸上回答问题 1 至问题 3。 【题目】 某文化产业集团委托软件公…

【flask】python框架flask的hello world

创建一个py文件&#xff0c;写如下内容 # save this as app.py from flask import Flaskapp Flask(__name__)app.route("/") def hello():return "Hello, World!"如下图 在此py文件路径下启动cmd&#xff0c;输入 flask run结果如下图 在浏览器中访问…

计算机毕业设计选题推荐-企业人事管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

算法练习: 矩阵置零

给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法 要实现这个功能&#xff0c;我们可以使用原地算法。首先&#xff0c;我们需要两个额外的数组来记录哪些行和列需要被置为0。然后&#xff0c;我们遍历整…

3600关成语填字APP游戏ACCESS\EXCEL数据库

成语类的APP游戏在最近一两年内非常的火爆&#xff0c;其主要原因是几乎所有中国人都能够冲个几十上百关&#xff0c;学习和趣味共享。看图猜成语类的数据之前已经弄到过很多&#xff0c;今天这份成语填字的倒是头一份。 该数据做成的APP效果如下&#xff1a; 数据以\符号分隔…

Windows 11 下使用 MSVC 2022 编译64位Nginx

一、软件准备 1、安装 Visual Studio 2022 包含单个组件&#xff1a; .NET Framework 4.6.1 目标包.NET Framework 4.6.1 SDKWindows 通用 C 运行时Windows 通用 CRT SDKMSVC v142 - VS 2019 C x64/x86 生成工具(v14.26)对 v142 生成工具(14.21)的 C/CLI 支持Clang compile fo…

Confluence8.5.14安装

一、Centos8、安装jdk11(略) 二、mysql数据库 1、mysql安装包下载: MySQL :: Download MySQL Community Server 2、安装: https://downloads.mysql.com/archives/get/p/23/file/mysql-8.0.37-1.el8.x86_64.rpm-bundle.tar tar -xvf mysql-8.0.37-1.el8.x86_64.rpm-bund…

【Hadoop|HDFS篇】HDFS概述

1. HDFS产出背景及定义 1.1 HDFS产生背景 随着数据量越来越大&#xff0c;在一个操作系统存不下所有的数据&#xff0c;那么就分配到更多的操作系 统管理的磁盘中&#xff0c;但是不方便管理和维护&#xff0c;迫切需要一种系统来管理多台机器上的文件&#xff0c;这 就是分布…

智能化升级:AI在客服知识库中的应用

引言 在数字化时代&#xff0c;客户服务已成为企业竞争的关键一环。随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;传统客服模式正经历着前所未有的变革。AI与客服知识库的深度融合&#xff0c;不仅极大地提升了客服处理的效率与准确性&#xff0c;还为用…