相信在使用Spring框架的过程中,很多小伙伴都发现内部提供了一种叫做事件的机制,今天的文章主要重点给各位读者系统地介绍关于事件的部分知识点。
其实事件并非是Spring官方专门创造出来的,在早期的JDK中就已经有事件设计的影子了。
JDK内部提供的事件机理
package org.idea.spring.framework.event; import java.util.EventListener; import java.util.EventObject; import java.util.Observable; import java.util.Observer; /** * @Author linhao * @Date created in 3:52 下午 2021/5/23 */ public class EventDemo { public static void main(String[] arg) { EventObservable observable = new EventObservable(); observable.addObserver(new EventObserver()); observable.notifyObservers("send message"); } static class EventObservable extends Observable { @Override protected synchronized void setChanged() { super.setChanged(); } @Override public void notifyObservers(Object data){ this.setChanged(); super.notifyObservers(new EventObject(data)); clearChanged(); } } static class EventObserver implements Observer, EventListener { @Override public void update(Observable o, Object msg) { EventObject eventObject = (EventObject) msg; System.out.println("接收数据:" + eventObject); } } } 复制代码
JDK这类的事件机制的设计思路是,将订阅事件Event的角色定义为Observer角色,然后统一将这些Observer存放到一个List集合中,每个Observer都会有一个专属的获取通知的函数(也就是代码中的update函数),Event如果需要通知到各个Observer只需要出发订阅者的update函数即可。
整体的设计思路大致如下图所示:
JDK提供的事件机制在实际使用的时候还是存在较多的问题。这里我结合上述的代码案例来进行解析:
触发通知之前需要手动开启一个开关检验逻辑,因为JDK内部源代码规定,如果没有开启开关,将不会通知到各个订阅方。
关于JDK的事件机制个人感觉使用起来不是那么友善,所以不是特别推荐使用,稍微了解即可。
ps:其实Spring内部的事件标准也是基于JDK的这套API进行改善的。
Spring内部事件的订阅
关于事件部分我打算先从实战讲起,毕竟感觉没有实战案例的原理分析都是在炫技能。通过相关的实战案例既能够让大家有更深入的感受,也能在工作中如果对这块技术生疏之后又快速恢复印象起来。
多事件接收案例:
package org.idea.spring.framework.event; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.ContextStartedEvent; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; /** * @Author linhao * @Date created in 4:30 下午 2021/5/23 */ @EnableAsync public class AnnotationEventDemo { public static void main(String[] args) { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.register(AnnotationEventDemo.class); annotationConfigApplicationContext.addApplicationListener(new ApplicationListener<ApplicationEvent>() { //这一块会优先执行 @Override public void onApplicationEvent(ApplicationEvent event) { System.out.println("=== application listener==="); } }); annotationConfigApplicationContext.refresh(); annotationConfigApplicationContext.start(); annotationConfigApplicationContext.close(); } //多事件处理的时候需要对参数做区别 监听数据处理的时候,需要注意先后顺序,因为反射中的getMethod是无顺序的 //@order的生效规则需要事件参数都是一致的时候才能生效 @EventListener @Async // @Order(1) public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) throws InterruptedException { System.out.println("EventListener -- refresh 接收事件参数:" + contextRefreshedEvent); } @EventListener @Async // @Order(2) public void onApplicationEvent2(ContextClosedEvent contextClosedEvent) throws InterruptedException { System.out.println("EventListener -- close 接收事件参数:" + contextClosedEvent); } @EventListener @Async // @Order(3) public void onApplicationEvent(ContextStartedEvent contextStartedEvent) throws InterruptedException { System.out.println("EventListener -- started 接收事件参数:" + contextStartedEvent); } } 复制代码
在Spring容器启动的过程中会发送多种事件,实际上我们可以对事件的类型进行分开监听,根据参数来识别。
例如监听Spring容器启动的事件
@EventListener事件使用的注意点
1.同步处理可能存在堵塞问题
这类事件监听机制实际上是同步的过程,按照上述的代码案例来说,如果在接收到Spring的refresh事件处理过程中,如果出现了堵塞情况,就会影响下边的started和close事件接收处理流程。
为了验证这一点可以在事件处理过程中让线程睡眠1秒钟观测下打印的效果
同步处理带来的不足点
如果希望解决这一问题,可以尝试启用异步处理机制。加上@Async注解即可
打印结果也有所不同
2.相同事件接收的先后问题
例如两个相同的事件 (一般是指在同一个bean内,接收事件时候参数一致的情况下) 接收之间有逻辑先后顺序的要求,可以对其加入@Order的注解进行区分
举个案例,当Spring容器准备进行销毁的时候,需要进行优雅关闭策略,那么这个时候需要指定几个关闭的环节,关闭的环节先后有一定的依赖顺序,代码案例如下图所示:
按照函数在代码的顺序从上到下依次执行即可保证优雅关闭的先后顺序,但是执行顺序却时而有序时而无序。
导致这一点的原因是:
Spring对于多事件监听的处理过程中会通过反射机制获取带有@EventListener注解的方法整理到一个数组集合中,然后便利去回调。但是在反射获取方法的时候,是需要通过JDK底层的getMethod获取的,但是getMehod获取到的Method集合本身是无顺序的,所以才会产生上边所说的那种现象。
为了避免这种现象的发生,可以加入一个@Order的注解来避免。如下所示:
自定义Spring事件订阅
这类事件我们在实际使用过程中可能会运用地更多。下边我们列举一个案例进行介绍:
首先定义一个Spring的事件
package org.idea.spring.framework.event; import org.springframework.context.ApplicationEvent; /** * @Author linhao * @Date created in 6:21 下午 2021/5/23 */ public class MyEvent extends ApplicationEvent { /** * Create a new {@code ApplicationEvent}. * * @param source the object on which the event initially occurred or with * which the event is associated (never {@code null}) */ public MyEvent(Object source) { super(source); } } 复制代码
然后再定义一个事件的发送和接收逻辑:
package org.idea.spring.framework.event; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.AnnotationConfigApplicationContext; /** * 事件发布器 * * @Author linhao * @Date created in 4:47 下午 2021/5/23 */ public class ApplicationEventPublisherDemo implements ApplicationEventPublisherAware { public static void main(String[] args) { AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(); annotationConfigApplicationContext.register(ApplicationEventPublisherDemo.class); annotationConfigApplicationContext.addApplicationListener(new ApplicationListener<MyEvent>() { @Override public void onApplicationEvent(MyEvent event) { System.out.println("application listener 接收到消息:" + event); } }); annotationConfigApplicationContext.refresh(); annotationConfigApplicationContext.close(); } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { System.out.println("===="); applicationEventPublisher.publishEvent(new MyEvent("hello world") { }); applicationEventPublisher.publishEvent("pay load event"); } } 复制代码
这就是一个简单的自定义Spring事件案例。
这些自定义的事件发送是如何实现通知的?
关于如何实现部分需要查阅一下源代码中的实现机制。
这里我将其中的逻辑分成了两个步骤:
1.对订阅者管理
在Spring的内部的这个个后置处理组件:
org.springframework.context.support.ApplicationListenerDetector
中,再对每个用户自定义事件的bean进行初始化之前会将其加入到一个Map当中,然后再初始化之后对这些个Map中记录过的bean加入到一个专门存放ApplicationListener对象的Set集合中。
2.对订阅者通知
以上边的案例代码作为讲解对象,当Spring容器进行了启动的最后一个环节,代码逻辑位于:
org.springframework.context.support.AbstractApplicationContext#finishRefresh
这里面有个事件发布的通知机制
根据源代码跟踪,你会看到这么一处地方:
这里涉及到一个叫做多播器的概念,Spring对于广播事件实际上封装了一个
org.springframework.context.event.ApplicationEventMulticaster 接口来进行抽象管理,这个接口有时候也被称之为 “多播器”,默认实现是
org.springframework.context.event.SimpleApplicationEventMulticaster
而对应的回调部分其实也就是位于
org.springframework.context.event.SimpleApplicationEventMulticaster#doInvokeListener中。
分析了Spring事件的源码实现思路,你会发现其实逻辑比较清晰明了。
ApplicationEvent内部定义的四种基本事件
- ContextRefreshedEvent事件
org.springframework.context.support.AbstractApplicationContext#finishRefresh 中进行触发 复制代码
- ContextClosedEvent事件
org.springframework.context.support.AbstractApplicationContext#doClose 中进行触发 复制代码
- ContextStartedEvent事件
org.springframework.context.support.AbstractApplicationContext#start中进行触发 复制代码
- ContextStoppedEvent事件
org.springframework.context.support.AbstractApplicationContext#stop中进行触发 复制代码
可以参考上述我所介绍的debug方式逐一查看相关源代码