Spring Boot(六)自定义事件及监听
事件及监听并不是SpringBoot的新功能,Spring框架早已提供了完善的事件监听机制,在Spring框架中实现事件监听的流程如下:
自定义事件,继承org.springframework.context.ApplicationEvent抽象类
定义事件监听器,实现org.springframework.context.ApplicationListener接口
在Spring容器中发布事件
实现自定义事件及监听
- 定义事件
//自定义事件
public class ApplicationEventTest extends ApplicationEvent { public ApplicationEventTest(Object source) {
super(source);
} /**
* 事件处理事项
* @param msg
*/
public void printMsg(String msg)
{
System.out.println("监听到事件:"+ApplicationEventTest.class);
}
}
- 定义监听器
//自定义事件监听器
//@Component
public class ApplicationListenerTest implements ApplicationListener<ApplicationEventTest> { @Override
public void onApplicationEvent(ApplicationEventTest event) { event.printMsg(null);
}
}
- 在Spring容器中发布事件
public static void main(String[] args) {
SpringApplication application = new SpringApplication(SpringbootdemoApplication.class);
//需要把监听器加入到spring容器中
application.addListeners(new ApplicationListenerTest());
Set<ApplicationListener<?>> listeners = application.getListeners();
ConfigurableApplicationContext context = application.run(args);
//发布事件
context.publishEvent(new ApplicationEventTest(new Object()));
context.close();
}
上面的示例是在SpringBoot应用中简单的测试一下。
实际开发中实现监听还有其他的方式,在Spring框架中提供了两种事件监听的方式:
编程式:通过实现ApplicationListener接口来监听指定类型的事件
注解式:通过在方法上加@EventListener注解的方式监听指定参数类型的事件,写该类需要托管到Spring容器中
在SpringBoot应用中还可以通过配置的方式实现监听:
3. 通过application.properties中配置context.listener.classes属性指定监听器
下面分别分析一下这三种监听方式
编程式实现监听
实现ApplicationListenser接口:
@Component
public class ApplicationListenerTest implements ApplicationListener<ApplicationEventTest> { @Override
public void onApplicationEvent(ApplicationEventTest event) { event.printMsg(null);
}
}
控制台输出测试:
public static void main(String[] args) {
SpringApplication application = new SpringApplication(SpringbootdemoApplication.class);
//需要把监听器加入到spring容器中
//application.addListeners(new ApplicationListenerTest());
//Set<ApplicationListener<?>> listeners = application.getListeners();
ConfigurableApplicationContext context = application.run(args);
//发布事件
context.publishEvent(new ApplicationEventTest(new Object()));
}
那么我们跟踪一下源码,看一下事件是如何发布出去的,又是如何被监听到的呢?
AbstractApplicationContext.java中截取部分代码
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Publishing event in " + getDisplayName() + ": " + event);
}
// Decorate event as an ApplicationEvent if necessary
/将object转成ApplicationEvent
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 {
// SimpleApplicationEventMulticaster 获取事件发布器,发布事件
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);
}
}
}
查看一下ApplicationContext类结构图可以发现:应用上下文AbstractApplicationContext实际还是通过继承ApplicationEventPublisher接口,实现了其中的事件发布的方法,使得Spring应用上下文有了发布事件的功能,在AbstractApplicationContext内部通过SimpleApplicationEventMulticaster事件发布类,将具体事件ApplicationEvent发布出去。

那么事件发布出去后又是如何被监听到的呢?下面看一下具Spring中负责处理事件发布类SimpleApplicationEventMulticaster 中multicastEvent方法具体实现过程
SimpleApplicationEventMulticaster.java部分代码,实际尝试将当前事件逐个广播到指定类型的监听器中(listeners已经根据当前事件类型过滤了)
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// getApplicationListeners(event, type) 筛选监听器,在context.publish(ApplicationEvent event)中已经将事件传入,getApplicationListeners中将可以根据这个event类型从Spring容器中检索出符合条件的监听器 for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
//尝试逐个向监听器广播
invokeListener(listener, event);
}
}
}

@EventListener注解方式实现
定义注解方法
@Component
public class MyEventHandleTest { /**
* 参数为Object类型时,所有事件都会监听到
* 参数为指定类型事件时,该参数类型事件或者其子事件(子类)都可以接收到
*/
@EventListener
public void event(ApplicationEventTest event){ event.printMsg(null);
} }
实现过程分析:
@EventListener注解主要通过EventListenerMethodProcessor扫描出所有带有@EventListener注解的方法,然后动态构造事件监听器,并将监听器托管到Spring应用上文中。
protected void processBean(
final List<EventListenerFactory> factories, final String beanName, final Class<?> targetType) { if (!this.nonAnnotatedClasses.contains(targetType)) {
Map<Method, EventListener> annotatedMethods = null;
try {
//查找含有@EventListener注解的所有方法
annotatedMethods = MethodIntrospector.selectMethods(targetType,
(MethodIntrospector.MetadataLookup<EventListener>) method ->
AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
}
catch (Throwable ex) {
// An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
}
}
if (CollectionUtils.isEmpty(annotatedMethods)) {
this.nonAnnotatedClasses.add(targetType);
if (logger.isTraceEnabled()) {
logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
}
}
else {
// Non-empty set of methods
ConfigurableApplicationContext context = getApplicationContext();
//遍历含有@EventListener注解的方法
for (Method method : annotatedMethods.keySet()) {
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
//动态构造相对应的事件监听器
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, targetType, methodToUse);
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
}
//将监听器添加的Spring应用上下文中托管
context.addApplicationListener(applicationListener);
break;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
beanName + "': " + annotatedMethods);
}
}
}
}
在application.properties中配置context.listener.classes
添加如下配置:
context.listener.classes=com.sl.springbootdemo.Listeners.ApplicationListenerTest
查看一下DelegatingApplicationListener类中实现逻辑:
public class DelegatingApplicationListener
implements ApplicationListener<ApplicationEvent>, Ordered { private static final String PROPERTY_NAME = "context.listener.classes"; private int order = 0;
//Spring framework提供的负责处理发布事件的类,前面说的Spring应用上下文中也是通过这个类发布事件的
private SimpleApplicationEventMulticaster multicaster; @Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
// getListeners内部实现读取context.listener.classes配置的监听器
List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
if (delegates.isEmpty()) {
return;
}
this.multicaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<ApplicationEvent> listener : delegates) {
this.multicaster.addApplicationListener(listener);
}
}
//发布事件
if (this.multicaster != null) {
this.multicaster.multicastEvent(event);
}
}
Spring-boot-{version}.jar包中提供一个类DelegatingApplicationListener,该类的作用是从application.properties中读取配置context.listener.classes,并将事件广播给这些配置的监听器。通过前面一章对SpringBoot启动流程分析,我们已经了解到SpringBoot启动时会从META-INF/spring.factories中读取key为org.springframework.context.ApplicationListener的所有监听器。DelegatingApplicationListener的功能可以让我们不需要创建META-INF/spring.factories,直接在application.properties中配置即可。
Spring Boot(六)自定义事件及监听的更多相关文章
- 深入理解Spring的容器内事件发布监听机制
目录 1. 什么是事件监听机制 2. JDK中对事件监听机制的支持 2.1 基于JDK实现对任务执行结果的监听 3.Spring容器对事件监听机制的支持 3.1 基于Spring实现对任务执行结果的监 ...
- uniapp仿h5+fire自定义事件触发监听
仿h5+fire自定义事件触发监听 uni-app调用 event.js 源码记录(点击查看) 1.js下载地址 [event.js](https://ext.dcloud.net.cn/plugin ...
- 前端Datatables自定义事件(监听Datatables插件一些常见的事件动作)
今天开发项目的时候,用Datatables插件做前端分页列表,想在列表发生翻页.排序.搜索.改变单页显示数据条数这些行为的时候做一些其他的操作,看了半天Datatables官网终于找到可以监测到这些事 ...
- Spring Boot 事件和监听
Application Events and Listeners 1.自定义事件和监听 1.1.定义事件 package com.cjs.boot.event; import lombok.Data; ...
- Spring笔记(7) - Spring的事件和监听机制
一.背景 事件机制作为一种编程机制,在很多开发语言中都提供了支持,同时许多开源框架的设计中都使用了事件机制,比如SpringFramework. 在 Java 语言中,Java 的事件机制参与者有3种 ...
- 009-Spring Boot 事件监听、监听器配置与方式、spring、Spring boot内置事件
一.概念 1.事件监听的流程 步骤一.自定义事件,一般是继承ApplicationEvent抽象类 步骤二.定义事件监听器,一般是实现ApplicationListener接口 步骤三.启动时,需要将 ...
- 【spring源码学习】spring的事件发布监听机制源码解析
[一]相关源代码类 (1)spring的事件发布监听机制的核心管理类:org.springframework.context.event.SimpleApplicationEventMulticast ...
- 【laravel】Eloquent 模型事件和监听方式
所有支持的模型事件 在 Eloquent 模型类上进行查询.插入.更新.删除操作时,会触发相应的模型事件,不管你有没有监听它们.这些事件包括: retrieved 获取到模型实例后触发 creatin ...
- Spring Boot(三):Spring Boot中的事件的使用 与Spring Boot启动流程(Event 事件 和 Listeners监听器)
前言:在讲述内容之前 希望大家对设计模式有所了解 即使你学会了本片的内容 也不知道什么时候去使用 或者为什么要这样去用 观察者模式: 观察者模式是一种对象行为模式.它定义对象间的一种一对多的依赖关系, ...
随机推荐
- 用Head方法获得百度搜索结果的真实地址
用Head方法获得百度搜索结果的真实地址 在百度中搜索"Java",第一条结果的链接为: https://www.baidu.com/link?url=HBOOMbhPKH4SfI ...
- java synchronized 关键字原理
Synchronized 关键字是解决并发问题常用解决方案,有以下三种使用方式: 同步普通方法,锁的是当前对象.同步静态方法,锁的是当前 Class 对象.同步块,锁的是 {} 中的对象. 实现原理: ...
- Batch Normalization 批量标准化
本篇博文转自:https://www.cnblogs.com/guoyaohua/p/8724433.html Batch Normalization作为最近一年来DL的重要成果,已经广泛被证明其有效 ...
- 破解Wifi
牛刀小试:Wifi破解的原理. 准备工具: 1:Kali Linux系统 2:一块好用的无线网卡 (推荐免驱版,网上也有推荐,可以去百度上google一下) 3:WPA字典(用来爆破抓获的握手包) ...
- tensorflow简单实现卷积前向过程
卷积,说白了就是对应位置相乘再求和,卷积操作用广泛应用于图像识别,在自然语言处理中也开始应用,用作文本分类问题. 卷积操作最重要的部分就是卷积核或者说过滤器 1.常用过滤器尺寸为3*3或者5*5 2. ...
- 20145234黄斐《信息安全系统设计基础》第八周(Linux下vim相关命令)
Linux下vim相关命令 在编辑程序时经常使用vim,所以记住一些常用的指令还是很有必要的 文件命令 vim file 打开单个文件vim file vim file1 file2 file3 .. ...
- 黑匣子_KEY
黑匣子 (box.pas/c/cpp) [ 问题描述] 某研究小组成员想发明一个黑匣子( 当然不是飞机上那个), 而是一个具有特殊功能的箱子. 这个箱子具有两个功能: 1. 存放一些正整数 x: 2. ...
- TCP/IP漫游
TCP/IP漫游 TCP/IP是互联网的基础协议栈,它包括大大小小几十个协议.本篇文章主要涉及到就是HTTP.TCP.IP协议.我们经常学的网络模型是七层或者五层,实际上一般认为一共只有四层就可以了. ...
- eclipse+tomcat配置远程debug调整
由于开发环境与真实服务器环境存在差异,有时开发时明明正常的逻辑,部署之后就会出现各种各样的问题,通过日志邮不能明确定位到问题的时候,可以采用远程debug调试来定位问题.下面就介绍一下具体的配置步骤: ...
- ubuntu 14.04 lts LAMP配置
一.目标 创建服务器环境,主要包括:Apache2.4.7 serverPHP 5.5.9Mysql 5.5.49扩展:MemcacheMcrypt 二.准备工作 1.服务器系统版本 Ubuntu s ...