相信在使用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方式逐一查看相关源代码
特殊事件PayloadApplicationEvent
Spring容器中除了提供有ApplicationEvent类型事件之外,还有提供一种叫做
PayloadApplicationEvent的事件类型(也是实现了ApplicationEvent接口)
这类事件设计的主要目的在于,发布事件的时候不一定需要定义一个Event对象,可以是直接发送字符串等类型。
使用案例:
package org.idea.spring.framework.event;
import org.springframework.context.*;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @Author linhao
* @Date created in 10:14 下午 2021/5/29
*/
public class PayLoadEventDemo2 implements ApplicationEventPublisherAware {
public ApplicationEventPublisher applicationEventPublisher;
public static void main(String[] args) {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
annotationConfigApplicationContext.register(PayLoadEventDemo2.class);
annotationConfigApplicationContext.register(MyPayloadApplicationEventListener.class);
annotationConfigApplicationContext.refresh();
PayLoadEventDemo2 payLoadEventDemo = annotationConfigApplicationContext.getBean(PayLoadEventDemo2.class);
payLoadEventDemo.applicationEventPublisher.publishEvent("hello");
payLoadEventDemo.applicationEventPublisher.publishEvent(new MyPayloadApplicationEvent<String>(payLoadEventDemo,"payload"));
annotationConfigApplicationContext.close();
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* 可以接收payload事件?
*/
static class MyPayloadApplicationEventListener implements ApplicationListener<MyPayloadApplicationEvent>{
@Override
public void onApplicationEvent(MyPayloadApplicationEvent myPayloadApplicationEvent) {
System.out.println("[MyPayloadApplicationEventListener] 接收到:%s,"+myPayloadApplicationEvent);
}
}
static class MyPayloadApplicationEvent<String> extends PayloadApplicationEvent<String> {
/**
* Create a new PayloadApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
* @param payload the payload object (never {@code null})
*/
public MyPayloadApplicationEvent(Object source, String payload) {
super(source, payload);
}
}
}
这段程序实际上个人感觉是有bug的,因为自己发送的payload事件并没有在监听器中收到消息。
后来在Spring内部源代码中的注释说明中跟多是提供给内部框架锁使用的,但是单从SpringFramework 5.2.3版本中的源代码看来,自己并没有看到太多的使用场景。而且关于PayLoad事件在使用过程中感觉源代码中对于事件类型的兼容性也不是太友善,所以不是太推荐使用。
(注意:使用@EventListener的时候,无法监听到PayLoad事件的发送)
事件内部的taskExecutor有何用处
在前边我们提到了可以基于注解的方式来实现异步线程的效果,但是注解的方式更多地是基于方法级别的粒度效果,如果希望实现全局性的事件处理异步化可以重构多播器中的线程池参数:
案例代码:首先需要定义多个Listener
package org.idea.spring.framework.event.multiEvent.listener;
import lombok.SneakyThrows;
import org.idea.spring.framework.event.multiEvent.events.MyAsyncEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* @Author linhao
* @Date created in 3:22 下午 2021/5/30
*/
@Component
public class Listener3 implements ApplicationListener<MyAsyncEvent> {
@SneakyThrows
@Override
public void onApplicationEvent(MyAsyncEvent event) {
System.out.println("3---" + event.getSource() + "thread is " + Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println("end 3");
}
}
package org.idea.spring.framework.event.multiEvent.listener;
import lombok.SneakyThrows;
import org.idea.spring.framework.event.multiEvent.events.MyAsyncEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* @Author linhao
* @Date created in 3:22 下午 2021/5/30
*/
@Component
public class Listener2 implements ApplicationListener<MyAsyncEvent> {
@SneakyThrows
@Override
public void onApplicationEvent(MyAsyncEvent event) {
System.out.println("2---" + event.getSource() + "thread is " + Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println("end 2");
}
}
package org.idea.spring.framework.event.multiEvent.listener;
import lombok.SneakyThrows;
import org.idea.spring.framework.event.multiEvent.events.MyAsyncEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
* @Author linhao
* @Date created in 3:22 下午 2021/5/30
*/
@Component
public class Listener1 implements ApplicationListener<MyAsyncEvent> {
@SneakyThrows
@Override
public void onApplicationEvent(MyAsyncEvent event) {
System.out.println("1---" + event.getSource() + "thread is " + Thread.currentThread().getName());
Thread.sleep(1000);
System.out.println("end 1");
}
}
一个自定义的Spring事件
package org.idea.spring.framework.event.multiEvent.events;
import org.springframework.context.ApplicationEvent;
/**
* @Author linhao
* @Date created in 3:14 下午 2021/5/30
*/
public class MyAsyncEvent 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 MyAsyncEvent(Object source) {
super(source);
}
}
一个配置全局性事件异步处理的config,这个配置生效之后,我们就不需要在每个事件接收端都配置@Async注解了
package org.idea.spring.framework.event.multiEvent.events;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.*;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 重写全局性事件异步发送组件
*
* @Author linhao
* @Date created in 3:29 下午 2021/5/30
*/
@Configuration
public class MyEventPublishConfig implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
ApplicationEventMulticaster applicationEventMulticaster = applicationContext.getBean(AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = (SimpleApplicationEventMulticaster) applicationEventMulticaster;
ExecutorService taskExecutor = Executors.newFixedThreadPool(10,new CustomizableThreadFactory("pool-idea-"));
simpleApplicationEventMulticaster.setTaskExecutor(taskExecutor);
simpleApplicationEventMulticaster.addApplicationListener(new ApplicationListener<ContextClosedEvent>() {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (!taskExecutor.isShutdown()) {
taskExecutor.shutdown();
System.out.println("shutdown");
}
}
});
}
}
最后是测试的service
@Service
public class EventTestService {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
public void doSendMsg(){
System.out.println(" ===== send ===== ");
applicationEventPublisher.publishEvent(new MyAsyncEvent("this is async"));
}
}
package org.idea.spring.framework.event.multiEvent;
import org.idea.spring.framework.event.multiEvent.service.EventTestService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.Resource;
/**
*
*
* @Author linhao
* @Date created in 3:13 下午 2021/5/30
*/
@SpringBootApplication
public class SpringApplicaitonDemo implements CommandLineRunner {
@Resource
private EventTestService eventTestService;
public static void main(String[] args) {
SpringApplication.run(SpringApplicaitonDemo.class);
}
@Override
public void run(String... args) throws Exception {
Thread.sleep(1000);
eventTestService.doSendMsg();
}
}
这样的全局性配置虽然能够减少我们在实际编码过程中异步化操作的繁琐步骤,但是针对的粒度也比较高。所以在实际运用的时候需要结合实际业务场景使用。