Spring及Springboot事件机制详解

news2024/11/24 10:41:48

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

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源码
	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, null);
	}

	@Override
	public 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 instead
					invokeListener(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> {

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("get event: " + event);
    }
}

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

如下示例


@Component
public class EventListenerByAnnotation {

    @EventListener
    public 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> {

    @Override
    public 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> {

    @Override
    public void onApplicationEvent(MyApplicationEvent event) {
        System.out.println(event);
    }
}
  1. 选择一个时间点发布事件。比如下面代码是在获得ApplicationContext对象是发布自定义事件。

@Component
public class MyEventPublisher implements ApplicationContextAware {

    @Override
    public 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.coloradmin.cn/o/2101129.html

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

相关文章

中资优配:“迪王”,拔得头筹!

Wind数据闪现&#xff0c;现在已有逾越190家上市公司入围券商9月“金股”组合。从举荐频次看&#xff0c;“迪王”比亚迪拔得头筹&#xff0c;阳光电源、我国移动等标的获券商密布举荐&#xff0c;医药生物等工作含“金”量高。 展望9月商场&#xff0c;券商判别&#xff0c;其…

真实:关于源代码防泄漏工作一些经验分享

互联网的飞速发展&#xff0c;各行各业都在往数字化转型&#xff0c;很多传统的业务数据都需要逐渐录入到电脑中&#xff0c;不仅节省了空间&#xff0c;还节约了成本。而这些数据都需要通过各类型的应用程序&#xff0c;进行分类&#xff0c;统计&#xff0c;展示&#xff0c;…

远程桌面连接出现身份验证错误解决方法

远程桌面身份验证错误的原因是什么&#xff1f; 在尝试远程连接到其他设备时&#xff0c;您可能会遇到身份验证错误&#xff0c;通常会弹出提示“出现身份验证错误&#xff0c;要求的函数不受支持”。这种身份验证错误通常是因为您输入的凭据与受控设备上设置的安全帐户信息不…

B端系统门门清之:CRM-客户管理系统,客户是一切的源头。

公司最宝贵的是什么&#xff0c;有人说是人才、有人说品牌、还有人说是技术&#xff0c;错&#xff0c;大错特错了&#xff0c;最宝贵的永远是客户&#xff0c;没有了客户&#xff0c;人才、品牌、技术都无从谈起。 今天分享最常见的B端系统CRM&#xff0c;那么是什么是CRM呢&…

大模型技术开发与应用

大模型技术开发与应用 LLM背景知识介绍: 大语言模型是一种人工智能模型,旨在理解和生成人类语言.大语言模型可以处理多种自然语言任务,如文本分类,问答,翻译,对话等等. 通常,大语言模型(LLM)是指包含数千亿(或更多)参数的语言模型(目前定义参数数量超过10B的模型为大语言模…

UI自动化测试 —— web端元素获取元素等待实践!

前言 Web UI自动化测试是一种软件测试方法&#xff0c;通过模拟用户行为&#xff0c;自动执行Web界面的各种操作&#xff0c;并验证操作结果是否符合预期&#xff0c;从而提高测试效率和准确性。 目的&#xff1a; 确保Web应用程序的界面在不同环境(如不同浏览器、操作系统)下…

注册登陆(最新版)

整体概述 本项目中&#xff0c;使用数据库连接池实现服务器访问数据库的功能&#xff0c;使用POST请求完成注册和登录的校验工作。 本文内容 本篇将介绍同步实现注册登录功能&#xff0c;具体的涉及到流程图&#xff0c;载入数据库表&#xff0c;提取用户名和密码&#xff0…

AquaCrop模型数据制备、优化方法、敏感性与气候变化影响分析及源码解析

AquaCrop是由世界粮食及农业组织&#xff08;FAO&#xff09;开发的一个先进模型&#xff0c;旨在研究和优化农作物的水分生产效率。这个模型在全球范围内被广泛应用于农业水管理&#xff0c;特别是在制定农作物灌溉计划和应对水资源限制方面显示出其强大的实用性。AquaCrop 不…

PySide6复杂C/S系统开发

PySide6复杂C/S系统开发 目录 0.了解 1.前提 2.实际代码&#xff08;继承QGraphicsview修改&#xff09; 0.了解 之前写了一篇“PySimpleGUI复杂C/S系统开发”博客&#xff08;由于pysimplegui更改了协议&#xff0c;因此不再推荐&#xff0c;用了pyside6后yyds&#xff09…

Transiting from CUDA to HIP (二)

一、Identifying Architecture Features 1. HIP_ARCH Defines 在 CUDA 编程中&#xff0c;__CUDA_ARCH__ 是一个预定义的宏&#xff0c;用于指示当前编译的代码所针对的 NVIDIA GPU 的计算能力&#xff08;Compute Capability&#xff09;。开发者可以使用这个宏来编写条件代…

Xinstall助力App推广:全方位支持,精准数据分析,你值得拥有

在如今的移动互联网时代&#xff0c;App推广已成为每个应用开发者必须面对的重要课题。然而&#xff0c;推广过程中往往伴随着诸多痛点&#xff0c;如数据混乱、投放盲目、决策滞后以及作弊困扰等。这些问题不仅影响了推广效果&#xff0c;还可能导致资源的浪费和投入产出不均衡…

数据安全认证来了解一下

随着数据安全法及个人信息保护法的实施&#xff0c;数据安全相关岗位在安全行业变得极为热门。 根据数据安全法第二十条&#xff0c;国家鼓励教育、科研机构和企业等开展数据开发利用技术和数据安全相关的教育和培训&#xff0c;采用多种途径培育专业人才&#xff0c;促进人才…

【Linux】使用Linux实现小程序 - 进度条

目录 一、缓冲区二、回车换行的概念三、进度条的设计3.1 版本1&#xff08;没有配合场景&#xff09;3.2 版本2&#xff08;配合场景&#xff09;3.3 版本3&#xff08;美化进度条&#xff09; 结尾 一、缓冲区 C/C语言&#xff0c;会针对标准输出&#xff0c;给我们提供默认的…

c++----杨辉三角(补充)

大家好。今天我给大家带来的是&#xff0c;我们以前讨论过的知识点。杨辉三角。我相信大家在c的学习中已经清楚的了解和认识到了杨辉三角的实现逻辑和实现方法了。但是应该大多局限于在c中吧。我们都知道我们c与c其实在一些地方是可以相互成就的。那么我们在c中的经典题目杨辉三…

自定义实现log4j的appender

log4j&#xff0c;应用最广泛的日志框架。其作者后来推出logback&#xff0c;也是好选择。不多说废话。 log4j组件介绍 Log4j主要有三个组件&#xff1a; Logger&#xff1a;负责供客户端代码调用&#xff0c;执行debug(Object msg)、info(Object msg)、warn(Object msg)、err…

oracle----undo表空间

文章目录 undo表空间概念和作用undo表空间主要用于解决&#xff1a;1.1 读一致性1.2 回滚事务1.3 实例恢复 undo表空间操作查看UNDO表空间查看UNDO 参数查看undo表空间文件位置 undo表空间概念和作用 对于DML语句&#xff0c;只要修改了数据块&#xff0c;数据库就会把修改前的…

使用Amazon SageMaker JumpStart微调Meta Llama 3.1模型以进行生成式AI推理

文章目录 使用Amazon SageMaker JumpStart微调Meta Llama 3.1模型以进行生成式AI推理Meta Llama 3.1SageMaker JumpStartSageMaker JumpStart中Meta Llama 3.1模型的微调配置使用SageMaker JumpStart UI进行无代码微调使用SageMaker JumpStart SDK进行微调结论 使用Amazon Sage…

电商数据整合新篇章:京东商品详情API返回值应用实践

电商数据整合在当今商业环境中具有重要地位&#xff0c;API&#xff08;应用程序编程接口&#xff09;提供了高效收集、整合和分析数据的途径。以京东商品详情API为例&#xff0c;通过其返回值&#xff0c;电商企业可以构建更精准的营销策略、优化产品以及提升用户体验。以下是…

实例分割【YOLOv8版】

参考文档 Segment - Ultralytics YOLO Docs​docs.ultralytics.com/tasks/segment/ 何为实例分割&#xff1f; 实例分割比目标检测更进一步&#xff0c;涉及识别图像中的各个对象并将它们与图像的其余部分分割开来。 实例分割模型的输出是一组用于勾勒图像中每个对象的掩码…

回溯法-图的m着色问题

图的 m 着色问题 问题描述 给定一个无向连通图 ( G (V, E) ) 和 ( m ) 种颜色&#xff0c;我们的任务是为图 ( G ) 的每个顶点着色&#xff0c;使得相邻的顶点颜色不同。如果存在这样的着色方案&#xff0c;我们称之为图 ( G ) 的 ( m ) 可着色问题。 算法思路 初始化&…