Spring容器的事件监听机制(简单明了的介绍)
前言
上一篇我们介绍了SpringFactoriesLoader。这一篇我接着来介绍一下Spring的另一个知识点,就是Spring容器的事件监听机制。
事件
说到事件,我们第一反应是什么是事件?其实 事件是发生在应用程序中的动作,比如点击按钮,在文本框中输入内容等操作都被称为事件。而当事件触发时,应用程序做出的一定的响应则表示应用监听了这个事件,而在服务器端,事件的监听机制更多的用于异步通知以及监控和异常处理。Java提供了实现事件监听机制的两个基础类:自定义事件类型扩展自java.util.EventObject
,事件的监听器扩展自java.util.EnventListener
。下面我们就以一个监控方法的耗时为例。
1. 定义事件
首先自定义事件类型,通常的做法是继承EnventObject类,随着事件的发生,相应的状态通常封装在此类中。在此处我们定义了一个时间戳,用于记录方法的开始执行时间。
/**
* 定义事件类型,通常的做法是继承 EnventObject,
* 随着事件的发生,相应的状态通常都封装在此类中。
* @author xiang.wei
* @date 2020/5/5 5:13 PM
*/
public class MethodMonitorEvent extends EventObject {
/**
* 时间戳,用于记录方法开始执行的时间
*/
protected long timestamp;
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public MethodMonitorEvent(Object source) {
super(source);
}
}
2. 定义监听器
事件类型定义好之后,接下来,我们还需要定义一个监听器,用于监听事件的发生。我们可以在方法开始执行之前发布一个begin事件,在方法结束之后发布一个end事件。我们定义了事件监听接口,里面定义了处理begin事件的方法onMethodBegin
和处理end事件的方法onMethodEnd
。我们注意到这两个方法都只有一个参数,就是MethodMonitorEvent 参数。说明这个监听器类只负责监听对应的事件并进行处理。
/**
* 定义事件监听接口
* @author xiang.wei
* @date 2020/5/5 5:24 PM
*/
public interface MethodMonitorEventListener extends EventListener {
/**
* 处理方法执行之前发布的事件
* @param event
*/
void onMethodBegin(MethodMonitorEvent event);
/**
* 处理方法结束时发布的事件
* @param event
*/
void onMethodEnd(MethodMonitorEvent event);
}
在这个监听器接口的实现类里我们将会具体实现这两个方法的逻辑。方法也是很简单
public class MethodMonitorEventListenerImpl implements MethodMonitorEventListener {
@Override
public void onMethodBegin(MethodMonitorEvent event) {
//记录方法开始执行时的时间
event.timestamp = System.currentTimeMillis();
}
@Override
public void onMethodEnd(MethodMonitorEvent event) {
//计算耗时
long duration = System.currentTimeMillis() - event.timestamp;
System.out.println("总耗时:" + duration+" ms");
}
}
3. 定义发布器
有了事件和监听器。我们还需要一个事件发布者,它本身作为一个事件源,在合适的时机,将相应的时间发布给对应的事件监听器。
public class MethodMonitorEventPublisher {
private List<MethodMonitorEventListener> eventListeners = new ArrayList<>();
public void methodMonitor() throws InterruptedException {
//定义事件
MethodMonitorEvent eventObject = new MethodMonitorEvent(this);
publishEvent("begin", eventObject);
//模拟方法执行:休眠5秒钟
TimeUnit.SECONDS.sleep(5);
publishEvent("end", eventObject);
}
//发布事件的逻辑
public void publishEvent(String status, MethodMonitorEvent event) {
//避免在事件处理期间,监听器被移除,这里为了安全做了一个复制操作
List<MethodMonitorEventListener> copyListeners = new ArrayList<>(eventListeners);
for (MethodMonitorEventListener listener : copyListeners) {
if ("begin".equals(status)) {
listener.onMethodBegin(event);
} else {
listener.onMethodEnd(event);
}
}
}
public static void main(String[] args) throws InterruptedException {
MethodMonitorEventPublisher publisher = new MethodMonitorEventPublisher();
//添加监听器
publisher.addEventListener(new MethodMonitorEventListenerImpl());
//发布事件
publisher.methodMonitor();
}
public void addEventListener(MethodMonitorEventListener listener) {
eventListeners.add(listener);
}
}
对于事件发布者(事件源)我们需要关注两点:
- 在合适的时机发布事件,此例中的
methodMonitor()
方法就是事件发布的源头,其在方法执行之前和结束之后两个时间点发布MethodMonitorEvent事件,每个时间点发布的事件都会传给相应的监听器进⾏处理。 - 事件监听器的管理:publisher 类中提供了事件监听器的注册和移除方法。这样客户端可以根据实际情况决定是否需要注册新的监听器或者移除某个监听器,如果没有提供remove方法,那么注册的监听器实例将一直被MethodMonitorEventPublisher引⽤,即使已经废弃不⽤了,也依然在发布者的监听器列表中,这会导致隐性的内存泄漏。
Spring容器的事件监听机制
说完了Java提供的事件监听机制的两个基础类,以及如何实现一个自定义事件的demo。下面就请出本篇文章的主角Spring容器的时间监听机制。
Spring的ApplicationContext容器内部中的所有事件类型均继承自org.springframework.context.ApplicationEvent
容器中的所有监听器都实现了org.springframework.context.ApplicationListener
接口
并且以bean的形式注册到了容器中,一旦容器内发布ApplicationEvent
及其子类型的事件,注册到容器中的ApplicationListener
就会对这些事件进行处理。ApplicationEventMulticaster
类是事件管理者,管理监听器和发布事件,ApplicationContext
通过委托ApplicationEventMulticaster
来发布事件
ApplicationEventPublisher
是事件发布者,该接口封装了事件有关的公共方法。
1.事件的继承类图
ApplicationContextEvent
继承自ApplicationEvent
,而ApplicationEvent
继承自EventObject
,Spring提供了一些默认的实现,比如:
ContextStartedEvent
表示容器在启动时发布的事件类型,即调用start()
方法。
ContextRefreshedEvent
表示容器在初始化或者刷新的时候发布的事件类型,如调用refresh()
方法,此处的实例化是指所有的bean都已经被加载。后置处理器被激活。所有单例bean都已被实例化。所有的容器对象都已准备好可使用。
ContextStoppedEvent
表示容器在即将关闭时发布的事件类型,即调用了stop()
方法。
监听器的继承类图
容器内部用ApplicationListener
作为事件监听器接口定义,它继承自EventListener
。ApplicationContext
容器在启动时,会自动识别并加载EventListener
类型的bean,一旦容器内有事件发布,将通知这些注册到容器的EventListener
。
其中ContextRefreshListener
监听器监听的是ContextRefreshedEvent
事件,而ContextCloserListener
监听器监听的是ContextClosedEvent
事件。
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}
而ApplicationContext
接口继承了ApplicationEventPublisher
接口,该接口提供了void publishEvent(ApplicationEvent event)
方法的定义,不难看出,ApplicationContext容器担当的就是事件发布者的角色。需要说明的是Spring事件默认是同步的,即调用publishEvent
方法发布事件后,它会处于阻塞状态,直到onApplicationEvent
接受到事件并处理返回之后才继续执行下去,这种单线程同步的好处是可以进行任务管理。
总结
本文首先介绍了Java中事件监听机制的基本概念,并且以一个记录方法耗时的demo说明了如何自定义事件类型。接着就是介绍了Spring容器的事件监听机制。
相关文章
- 从观察者模式谈论 : Spring Boot中创建、发布和侦听自定义事件
- 深入实践Spring Boot3.2.5 分页查询控制器
- 《Spring Data 官方文档》5.6 保存, 更新, 以及删除数据行
- Spring Cache + Redis;用Spring Cache的注解自动管理Redis缓存
- 微服务容器化迁移——在K8s中部署Spring Cloud
- 手把手教你搭建Spring Boot+Vue前后端分离
- spring发布和接收定制的事件(spring事件传播)
- 基于Spring+Spring MVC+Mybatis的B2C购物网站
- 磕一磕:Spring boot 打包后静态资源及JSP等资源访问不到问题
- spring中@Autowire和@Resource的区别在哪里?
- 不能吧,最后一次它又来了--整合Mybatis和事务管理(“最易懂得Spring学习”)
- 【spring框架】通过XML配置事务(Transaction)
- Java开发 - 单点登录初体验(Spring Security + JWT)
- 玩转spring boot——结合AngularJs和JDBC
- 玩转spring boot——ajax跨域
- spring容器加载完毕做一件事情(利用ContextRefreshedEvent事件)
- 曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)
- 事物Spring boot @Transactional