Spring 事件驱动,自定义事件、应用监听器

2023-10-27


 

概述

spring引入了事件机制,支持应用事件 ApplicationEvent、应用监听器 ApplicationListener。
 

事件驱动的3要素

  • 事件:具有事件源、发生时间2个属性
  • 事件发布器:负责发布事件
  • 事件监听器:监听指定类型的事件,
     

spring的事件驱动模型

观察者模式的典型应用,监听器订阅指定类型的事件,事件发布器发布事件时,会自动通知订阅了该事件类型的所有监听器。

 

源码分析

ApplicationEvent 应用事件

public abstract class ApplicationEvent extends EventObject {

	private static final long serialVersionUID = 7099057708183571937L;

	//事件的发生时间,long型时间戳
	private final long timestamp;

	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}

	public ApplicationEvent(Object source, Clock clock) {
		super(source);
		this.timestamp = clock.millis();
	}

	public final long getTimestamp() {
		return this.timestamp;
	}

}

jdk自带的对象事件 EventObject 定义了一个Object类型的成员变量表示事件源,ApplicationEvent 在 EventObject 的基础上增加了一个long型的成员变量记录事件的发生时间。

 

spring中常见的事件类型

ApplicationEvent 表示spring的应用事件,是应用事件的基类,实现类众多,对应具体的事件类型。
 

spring常见的事件类型(5种标准事件)

方法名称 触发时机
ContextStartedEvent
上下文开始事件
refresh()刷新过程中,调用高级容器的 start() 方法开启高级容器(的生命周期管理)时触发
ContextRefreshedEvent
上下文已刷新事件
refresh() 完成刷新时触发
ContextStoppedEvent
上下文已停止事件
容器关闭过程中,调用高级容器的 stop() 方法终止容器(的生命周期管理)时触发
ContextClosedEvent
上下文已关闭事件
容器关闭完成后触发
RequestHandledEvent
请求已处理事件
请求处理完成时触发
如果是 springmvc,会直接使用 子类事件 ServletRequestHandledEvent

4+1,4个容器|上下文生命周期相关的,1个处理请求相关的。

spring支持自定义的应用事件,可以继承 ApplicationEvent 实现自定义的应用事件。

 

ApplicationListener 应用监听器

//继承了jdk自带的事件监听器 EventListener,泛型指定要监听的事件类型
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

	//处理事件
	void onApplicationEvent(E event);

	static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
		return event -> consumer.accept(event.getPayload());
	}

}

 

ApplicationEventPublisher 事件发布器

@FunctionalInterface
public interface ApplicationEventPublisher {

	//发布事件,默认实现是调用下面的方法
	default void publishEvent(ApplicationEvent event) {
		publishEvent((Object) event);
	}

	//发布事件,参数是要发布的事件
	void publishEvent(Object event);

}
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
		MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
	//...
}

AbstractApplicationContext 实现了事件发布器,可以调用AbstractApplicationContext实例发布事件。

 

自定义事件

/**
 * 自定义事件,需要继承 ApplicationEvent
 */
public class MyApplicationEvent extends ApplicationEvent {

    /**
     * 在构造方法中调用父类对应的构造方法
     *
     * @param source 指定事件源
     */
    public MyApplicationEvent(Object source) {
        super(source);
    }

}

 

自定义应用监听器

方式一 ApplicationListener接口+放到容器中

//实现ApplicationListener接口,泛型指定要监听的事件类型,ApplicationEvent 则表示监听所有的应用事件
@Component  //放到容器中
public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> {

    /**
     * 处理事件
     *
     * @param event
     */
    @Override
    public void onApplicationEvent(MyApplicationEvent event) {
        //...
    }

}

一个类对应一个应用监听器,往往需要编写多个自定义的应用监听器类

 

方式二 @EventListener注解

@Component  //标注为bean,这样才会解析其中的 @EventListener 注解
public class MyApplicationListener {

    /**
     * 使用 @EventListener 标注为应用监听器,形参类型即要监听的事件类型
     *
     * @param event 事件
     */
    @EventListener
    public void MyApplicationEvent(MyApplicationEvent event) {
        //...
    }

}

一个方法对应一个应用监听器,可在一个类中写多个应用监听器

 

方式三 ApplicationListener接口+代码配置

此种方式适合springboot应用。
 
自定义的应用监听器

//不需要、也不能放到容器中,否则会出现重复创建监听器实例、重复监听的情况
public class MyApplicationListener implements ApplicationListener<MyApplicationEvent> {

    @Override
    public void onApplicationEvent(MyApplicationEvent event) {
        //...
    }

}

 
修改main()方法如下

//创建spring容器
SpringApplication springApplication = new SpringApplication(DemoApplication.class);
//添加应用监听器
springApplication.addListeners(new MyApplicationListener());
//启动容器
springApplication.run(args);

 

必看的注意事项 | 使用建议

1、第一、二种方式

这2种方式都会把监听器放到容器中,在 refersh() 刷新上下文时会通过 beanFactory.getBean() 创建监听器实例放到容器中。监听器类|方法中可以使用 @Value、@Autowired、@Bean之类的spring注解。
 

如果应用监听器中用于处理事件的回调方法执行很耗时、逻辑上允许异步执行,又是springboot应用,那么可以在方法上标注 springboot自带的 @Async 注解,将方法标注为异步方法,让方法异步执行,不阻塞主线程。

引导类上需要加 @EnableAsync,@Async 才会生效。

 

2、第三种方式

通过spring的factories机制实现,以springboot应用为例,在上下文|容器创建之前就创建了应用监听器实例,绑定到运行监听器内置的事件广播器上,生效比较早,这种方式定义的监听器通常用于在启动阶段监听默认的运行监听器发布的事件。

是通过反射调用无参构造器创建实例,不需要、也不能使用@Component之类的注解放到容器中,否则会出现重复创建监听器实例、重复监听的情况。

因为是在上下文|容器创建之前就创建监听器实例,类上也不使用@Component之类的注解,所以不能在监听器类中使用 @Value、@Autowired、@Bean之类的spring注解,或者说这些注解无效。

 

3、使用建议

通常使用第一二种即可。

第三四种创建监听器实例的时机比较早,可以监听高级容器 refresh() 之前的事件,比如 默认的运行监听器发布的时间,如果在refresh()之前就要监听事件,可以使用第三种。

需要注意的是,第三种是通过反射调用无参构造器创建实例,第四种是直接new调用构造方法创建实例,创建的监听器实例都不会放到容器中,但高级容器refresh()后,这些监听器实例依然有效、起作用。

 

发布事件

可以通过高级容器 AbstractApplicationContext、或者事件广播器 ApplicationEventMulticaster 发布事件,这2个都会作为bean自动放到容器中,直接注入使用即可

@Autowired
private ApplicationContext applicationContext;

@Autowired
private ApplicationEventMulticaster applicationEventMulticaster;


public void publish() {
    //事件,参数指定事件源
    MyApplicationEvent myApplicationEvent = new MyApplicationEvent(this);
    
    //通过 applicationEventMulticaster 发布事件
    applicationEventMulticaster.multicastEvent(myApplicationEvent);
    
    //通过 applicationContext 发布事件
    applicationContext.publishEvent(myApplicationEvent);
}

作为事件广播器来说,ApplicationContext 是高级容器,包含了太多东西,偏重量级,ApplicationEventMulticaster 比较纯粹,更推荐用 ApplicationEventMulticaster。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring 事件驱动,自定义事件、应用监听器 的相关文章

随机推荐

  • Session和Cookie实现购物车

    来自森大科技官方博客 http www cnsendblog com index php p 342 GPS平台 网站建设 软件开发 系统运维 找森大网络科技 http cnsendnet taobao com 使用Session和Cook
  • 自定义Mybatis框架

    目录 自定义Mybatis分析 轮子缺少的配件 组装轮子 制定骨架 解析配置文件 类关系梳理 创建默认实现类 实现基于注解的查询 目录结构 流程图 通过快速入门示例 Mybatis快速入门 我们发现使用 mybatis 是非常容易的一件事情
  • easyui dialog 子窗口jsp(被弹出窗口)调用父jsp页面方法操作父jsp

    父jsp monthDuty jsp 选中tab2 var selectTabByIndex function tabId tabs select 1 中间js文件 monthDutyJs js var dialog parent sunn
  • 「Linux-基础」CentOS 8 LVM逻辑卷管理

    LVM逻辑卷管理 枫梓林 提示 建议按着步骤来 文章目录 LVM逻辑卷管理 1 简介 2 建立LVM的步骤 3 逻辑卷管理及部署 1 磁盘分区 2 物理卷管理 建立物理卷 扫描物理卷 显示物理卷 删除物理卷 3 卷组管理 建立卷组 扫描卷组
  • STM32(HAL库)通过ADC读取MQ2数据

    目录 1 简介 2 CubeMX初始化配置 2 1 基础配置 2 1 1 SYS配置 2 1 2 RCC配置 2 2 ADC外设配置 2 3 串口外设配置 2 4 项目生成 3 KEIL端程序整合 3 1 串口重映射 3 2 ADC数据采集
  • 实验5-8 使用函数求圆台体积 (10 分)

    实验5 8 使用函数求圆台体积 10 分 本题要求实现函数求圆台体积 定义并调用函数volume tc r lower r upper h 计算下底半径为r lower 上底半径为r upper 高度为h的圆台的体积 函数类型是double
  • 卷积学习与传统稀疏编码、ICA模型学习区别(逐步补充)

    逐步总结 有待补充 无监督学习知识框架 这种分类不合适 稀疏编码等也可以从统计学角度看做模型学习与参数选择 实际上 稀疏编码是从1维信号发展起来的表示方法 近年来 稀疏编码逐渐引入信号的先验信息 由非模型向基于模型的转变 学习特色字典 单层
  • iOS开发Swift-12-列表UI,TableViewController,动态响应Button勾选-待办事项App(1)

    1 创建新项目 为项目添加图标 2 将Table View Controller添加到界面中 将箭头移动到Table View上来 代表它是首页 根页面 选中ViewController 点击Delete 对它进行删除 将代码ViewCon
  • 网络/网络编程

    网络 网络编程部份 1 connect方法会阻塞 请问有什么方法可以避免其长时间阻塞 答 最通常的方法最有效的是加定时器 也可以采用非阻塞模式 2 网络中 如果客户端突然掉线或者重启 服务器端怎么样才能立刻知道 答 若客户端掉线或者重新启动
  • 服务器文件直接复制到本地,本地文件直接复制到云服务器

    本地文件直接复制到云服务器 内容精选 换一换 在本地主机和Windows弹性云服务器上分别安装QQ exe等工具进行数据传输 使用远程桌面连接mstsc方式进行数据传输 该方式不支持断点续传 可能存在传输中断的情况 因此不建议上传大文件 文
  • MySQL 5.6解压缩版安装配置方法图文教程(win10)

    MySQL 5 6解压缩版安装配置方法图文教程 win10 1 首先 我们需要得到这个安装包 解压下载到本地 可在小编网盘中找到 我已共享链接 可直接下载 比如 现在我们把他解压到我们本地的D MySQL文件夹下 现在我们就可以看到 当前目
  • 【WiFi】Wi-Fi 6(802.11ax)解析24:802.11ax中MU-MIMO和OFDMA的区别

    目录 1 序言 2 OFDMA 2 MU MIMO 3 MAC层部分 MU MIMO和OFDMA 4 结语 5 参考 1 序言 笔者将自己对于802 11ax中的MU MIMO和OFDMA的区别做了一个简单的总结 因为很多非通信技术专业的童
  • 临界区对象TCriticalSection(Delphi) 与 TRtlCriticalSection 的区别

    TRtlCriticalSection 是一个结构体 在windows单元中定义 TCriticalSection是在SyncObjs单元中实现的类 它对上面的那些临界区操作API函数进行了了封装 简化并方便了在Delphi的使用 如TCr
  • 用C语言开发一个BT下载软件 (四) ------ 代码实现-1-种子文件解析模块

    parse metafile h ifndef PARSE METAFILE define PARSE METAFILE 保存从种子文件中获取的tracker的URL typedef struct Announce list char an
  • 数据库建索引规则

    数据库建立索引常用的规则如下 1 表的主键 外键必须有索引 2 数据量超过300的表应该有索引 3 经常与其他表进行连接的表 在连接字段上应该建立索引 4 经常出现在Where子句中的字段 特别是大表的字段 应该建立索引 5 索引应该建在选
  • 微信小程序分包-主包尺寸 (不包合插件) 应小于 1.5 M

    目录 起因 分包 最后 起因 更新一个之前的小程序 上传的时候提示主包尺寸 不包合插件 应小于 1 5 M 怎么办 查看教程啊 开发者可通过开发者工具中的性能扫描工具提前发现代码中的可优化项 1 代码包不包含插件大小超过 1 5 M 建议
  • 迭代(iterable)和迭代器

    迭代 iterable 任何可迭代对象都可以作用于for循环 包括我们自定义的数据类型 只要符合迭代条件 就可以使用for循环 d a 1 b 2 c 3 对dict迭代 for k v in d items 如果要同时迭代key和valu
  • 文件操作之日志文件

    一 前言 用户 那谁 你的程序出问题了 来看看 你 问题是怎么出现的 为了复现 用户 我也不知道 就这样点点点就出问题了 你 这个时候 日志文件的重要性就凸显出来了 别指望用户能描述清楚问题出现的经过 最靠谱的还是看日志文件 二 多说两句
  • 网站开发————JavaEE/实训实训实训

    JavaEE 搭建Maven 1 下载 2 环境变量 3 设置 依赖包位置 IntelliJ 1 链接Maven 2 建项目 无框架 3 建Model maven webapp 4 建java resoure并关联 4 导入依赖到pom x
  • Spring 事件驱动,自定义事件、应用监听器

    目录 概述 源码分析 ApplicationEvent 应用事件 spring中常见的事件类型 ApplicationListener 应用监听器 ApplicationEventPublisher 事件发布器 自定义事件 自定义应用监听器