常见事件监听机制的主要角色如下:
事件及事件源:事件源发生某事件是特定事件监听器被触发的原因;
事件监听器:监听器监听特定事件,并在内部定义了事件发生后的响应逻辑;
事件发布器:事件监听器的容器,对外提供发布事件和增删事件监听器的接口,维护事件和事件监听器之间的映射关系,并在事件发生时负责通知相关监听器。
JDK为提供了一个代表所有可被监听事件的事件基类 java.util.EventObject
,所有自定义事件类型都必须继承该类:
public class EventObject implements java.io.Serializable {
private static final long serialVersionUID = 5516075349620653480L;
/**
* 事件源对象
*/
protected transient Object source;
/**
* 构建一个原型事件,source不能为null
*/
public EventObject(Object source) {
if (source == null)
throw new IllegalArgumentException("null source");
this.source = source;
}
public Object getSource() {
return source;
}
public String toString() {
return getClass().getName() + "[source=" + source + "]";
}
}
JDK 还提供了一个对所有事件监听器进行抽象的接口 java.util.EventListener
,这是一个标记接口,所有自定义事件监听器都必须实现该标记接口:
/*
* 所有事件侦听器接口都必须扩展的标记接口
*/
public interface EventListener {
}
以监听任务执行完毕,发送邮件为例:
1、定义事件源
@Data
@AllArgsConstructor
public class Task {
/**
* 任务名
*/
private String name;
/**
* 任务执行结果
*/
private Integer code;
}
2、定义事件
@Getter
public class TaskEvent extends EventObject {
/**
* 消息内容
*/
private String message;
/**
* 消息接收人
*/
private String receiver;
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public TaskEvent(Object source, String message, String receiver) {
super(source);
this.message = message;
this.receiver = receiver;
}
}
3、定义事件监听器
/**
* @Description 定义统一事件监听处理接口
* @Author jidi
*/
public interface TaskEventListener extends EventListener {
/**
* 任务完成处理逻辑
*/
void handleTask(TaskEvent event);
}
/**
* @Description 定义邮件发送监听器
* @Author jidi
*/
@Slf4j
@Data
public class MailTaskListener implements TaskEventListener {
private static final Integer OK = 200;
@Override
public void handleTask(TaskEvent event) {
Task source = (Task)event.getSource();
if(Objects.equals(source.getCode(), OK)){
/**
* 模拟发送邮件
*/
log.info("尊敬的{},您好!任务-{}已执行完毕!{}", event.getReceiver(), source.getName(), event.getMessage());
}
}
}
4、定义事件发布器
public class TaskEventPublisher {
/**
* 保存监听器
*/
private List<TaskEventListener> listeners = new ArrayList<>();
/**
* 注册监听器
*/
public synchronized void register(TaskEventListener listener){
if(!listeners.contains(listener)){
listeners.add(listener);
}
}
/**
* 移除监听器
*/
public synchronized boolean unregister(TaskEventListener listener){
if(listeners.isEmpty()){
return true;
}
return listeners.remove(listener);
}
/**
* 发布事件
*/
public void publishEvent(TaskEvent event){
// 循环调用
listeners.stream().forEach(listener -> listener.handleTask(event));
}
}
5、测试
public static void main(String[] args) {
/**
* 初始化事件源
*/
Task task = new Task("订单10001开始发货!", 200);
/**
* 初始化事件
*/
TaskEvent event = new TaskEvent(task, "回复TD退订", "基地");
/**
* 初始化监听器
*/
MailTaskListener listener = new MailTaskListener();
/**
* 初始化发布器
*/
TaskEventPublisher publisher = new TaskEventPublisher();
/**
* 注册监听器
*/
publisher.register(listener);
/**
* 发布事件
*/
publisher.publishEvent(event);
}
Spring 框架对事件的发布与监听提供了相对完整的支持,它扩展了JDK中对自定义事件监听提供的基础框架(java.util.EventObject
和 java.util.EventListener
),并与Spring的IOC特性作了整合,使得用户可以根据自己的业务特点进行相关的自定义,并依托Spring容器方便的实现监听器的注册和事件的发布。
Spring 事件机制有几个重要的接口:
1、ApplicationEvent
,Spring 提供的事件抽象类,继承自 java.util.EventObject
;
2、ApplicationListener
,Spring 提供的事件监听器接口,继承自 java.util.EventListener
,提供了一个 onApplicationEvent
方法,用于执行具体的事件处理逻辑;
3、ApplicationEventPublisher
,Spring 提供的事件发布接口,ApplicationContext
实现了该接口,该接口提供了一个publishEvent
方法,用来发布事件;
4、ApplicationEventMulticaster
,Spring 事件机制中的事件广播器,默认实现 SimpleApplicationEventMulticaster
,该组件会在容器启动时被自动创建,并以单例的形式存在,管理了所有的事件监听器,并提供针对所有容器内事件的发布功能。
执行流程
Spring 事件执行流程为:当一个事件源产生事件时,它经过事件发布器 ApplicationEventPublisher
发布事件,而后事件广播器ApplicationEventMulticaster
会去事件注册表 ApplicationContext
中找到事件监听器 ApplicationListnener
,而且逐个执行监听器的 onApplicationEvent
方法,从而完成事件监听器的逻辑。
Spring 提供5种标准的事件监听:
1、ContextRefreshedEvent
:上下文更新事件,会在 ApplicationContext
被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext
接口中的 refresh()
方法时被触发;
2、ContextStartedEvent
:上下文开始事件,当容器 ConfigurableApplicationContext
的 start()
方法开始/重新开始容器时触发该事件;
3、ContextStoppedEvent
:上下文停止事件,当容器 ConfigurableApplicationContext
的 stop()
方法停止容器时触发该事件;
4、ContextClosedEvent
:上下文关闭事件,当 ApplicationContext
被关闭时触发该事件;
5、RequestHandledEvent
:请求处理事件,在web应用中,当一个http请求结束会触发该事件。
Spring Boot扩展了 Spring的 ApplicationContextEvent
,提供了四种事件:
1、ApplicationStartedEvent
: spring boot启动开始时触发;
2、ApplicationEnvironmentPreparedEvent
:spring boot 对应 Enviroment
已经准备完毕会触发,此时上下文还没创建;
3、ApplicationPreparedEvent
:spring boot上下文 context
创建完成触发,但此时 spring 中的 bean 是没有完全加载完成的;
4、ApplicationFailedEvent
:spring boot 启动异常时触发。
自定义 Spring 事件监听的流程分4步:
1、自定义事件,继承 ApplicationEvent
抽象类;
2、定义事件监听器:
a、实现接口 ApplicationListener
,重写 onApplicationEvent
方法来自定义相关事件的处理逻辑;
b、在某个方法上使用 @EventListener
注解即可。
3、注册监听器:
a、通过 SpringApplication
的 addListeners
方法,手动注册;
b、通过注解 @Component
将监听器加入到容器中;
c、在配置文件中通过 context.listener.classes
指定监听器。
4、发布事件,触发监听。使用实现了 ApplicationEventPublisher
接口的类(常用 ApplicationContext
)的publishEvent(ApplicationEvent event)
方法发布事件。
自定义事件
通过继承 ApplicationEvent
来自定义事件:
@Getter
@Setter
public class MessageEvent<T> extends ApplicationEvent implements Serializable {
private static final long serialVersionUID = 3529299915023264729L;
/**
* 消息类型
*/
private Integer type;
/**
* 消息内容
*/
private T content;
public MessageEvent(Object source, Integer type, T content) {
super(source);
this.type = type;
this.content = content;
}
}
注意:一定要显示创建默认的构造方法,构造器的参数为该事件的相关数据对象,监听器可以获取到该数据对象,进而进行相关逻辑处理。
定义事件监听器
方式一,实现接口 ApplicationListener
,重写 onApplicationEvent
方法:
@Slf4j
public class MessageEventListenter<T> implements ApplicationListener<MessageEvent<T>> {
@Override
public void onApplicationEvent(MessageEvent<T> event) {
Integer type = event.getType();
T content = event.getContent();
// 发短信
if(type == 1){
log.info("模拟发送短信,短信内容为:{}", content);
}
// 发邮件
else if(type == 2){
log.info("模拟发送邮件,邮件内容为:{}", content);
}
// 站内信
else {
log.info("模拟发送站内信,站内信内容为:{}", content);
}
}
}
方式二,使用注解@EventListener
:
@Slf4j
public class MessageEventHandler<T> {
/**
* 1、被@EventListener注解方法必须满足: 最多只能有一个参数
* 2、若只是监听一种事件,参数类型应为该事件对象类(该事件的子类事件,也会被监听到)
* 3、设置 classes 属性指定多个事件来监听多种事件且保证这个方法无参
* 4、设置 condition 属性可以实现对事件进行选择性处理
*
*/
@EventListener(classes = MessageEvent.class)
public void handlerMessageEvent(MessageEvent<T> event){
Integer type = event.getType();
T content = event.getContent();
// 发短信
if(type == 2){
log.info("模拟发送短信,短信内容为:{}", content);
}
// 发邮件
else if(type == 1){
log.info("模拟发送邮件,邮件内容为:{}", content);
}
// 站内信
else {
log.info("模拟发送站内信,站内信内容为:{}", content);
}
}
}
注册监听器
注册监听器,其实质就是将监听器进行IOC处理,让 spring 容器管理监听器的生命周期。 在SpringBoot中有三种方式可以将自定义的监听器纳入spring容器管理:
具体实现 | 备注 |
---|---|
在SpringBoot启动类的main方法中,使用 SpringApplication 的 addListeners 方法进行注册 |
进行单元测试时,(由于不会走SpringBoot启动的类main方法,所以)此方式不生效。 也不能搭配 @Async 注解,进行异步监听。 |
通过 @Component 或类似注解,将监听器(或监听器方法所在的)纳入容器管理 |
推荐使用方式,能够搭配@Async 注解,进行异步监听。 |
在SpringBoot配置文件中指定监听器(或监听器方法所在的)类 | 多个监听器,使用逗号分割即可。 不能搭配 @Async 注解,进行异步监听。 |
方式一,使用 SpringApplication
的 addListeners
方法进行注册:
public static void main(String[] args) {
SpringApplication application = new SpringApplication(AnswerAdminApplication.class);
// 添加监听器
application.addListeners(new MessageEventListenter<String>());
application.run(args);
}
方式二,使用注解将监听器纳入容器管理:
/**
* 使用@Component,纳入spring容器管理
*/
@Component
@Slf4j
public class MessageEventHandler<T> {
/**
* 1、被@EventListener注解方法必须满足: 最多只能有一个参数
* 2、若只是监听一种事件,参数类型应为该事件对象类(该事件的子类事件,也会被监听到)
* 3、设置 classes 属性指定多个事件来监听多种事件且保证这个方法无参
* 4、设置 condition 属性可以实现对事件进行选择性处理
*
*/
@EventListener(classes = MessageEvent.class)
public void handlerMessageEvent(MessageEvent<T> event){
Integer type = event.getType();
T content = event.getContent();
// 发短信
if(type == 2){
log.info("模拟发送短信,短信内容为:{}", content);
}
// 发邮件
else if(type == 1){
log.info("模拟发送邮件,邮件内容为:{}", content);
}
// 站内信
else {
log.info("模拟发送站内信,站内信内容为:{}", content);
}
}
}
/**
* 使用@Component,纳入spring容器管理
*/
@Component
@Slf4j
public class MessageEventListenter<T> implements ApplicationListener<MessageEvent<T>> {
@Override
public void onApplicationEvent(MessageEvent<T> event) {
Integer type = event.getType();
T content = event.getContent();
// 发短信
if(type == 1){
log.info("模拟发送短信,短信内容为:{}", content);
}
// 发邮件
else if(type == 2){
log.info("模拟发送邮件,邮件内容为:{}", content);
}
// 站内信
else {
log.info("模拟发送站内信,站内信内容为:{}", content);
}
}
}
方式三,使用配置文件设置监听器
# 配置监听器
context.listener.classes: com.vvupup.answer.admin.event.MessageEventListenter
事件发布
调用 ApplicationEventPublisher
接口的 publishEvent
方法,发布事件:
/**
* @Description Spring 事件发布测试
* @Author jidi
*/
@RestController
@RequestMapping("/event")
public class TestController {
@Autowired
private ApplicationContext applicationContext;
@GetMapping("/pub")
public String test(){
/**
* 发布事件
*/
applicationContext.publishEvent(new MessageEvent<>(this, 1, "短信"));
return "ok";
}
}
上面的例子实现事件监听使用的是同步方式,还可以结合注解 @Async
实现异步事件监听:
/**
* 1.开启异步,自定义线程池(非必须,默认会使用 SimpleAsyncTaskExecutor )
*/
@Configuration
@EnableAsync // @EnableAsync 注解开启异步
public class AsyncConfig extends AsyncConfigurerSupport {
/**
* 自定义线程池
*/
@Bean("executor")
public Executor executor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 线程池核心线程数量
executor.setCorePoolSize(5);
// 队列最大长度
executor.setQueueCapacity(200);
// 最大线程数量
executor.setMaxPoolSize(10);
// 线程池拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 线程名前缀
executor.setThreadNamePrefix("async-");
// 允许空闲时间
executor.setKeepAliveSeconds(30);
// 初始化
executor.initialize();
return executor;
}
/**
* 如果有多个线程池,指定要使用的线程池
*/
@Override
public Executor getAsyncExecutor() {
return executor();
}
}
/**
* 2、使用 @Async 注解,开启异步处理
*/
@Data
@Slf4j
public class MessageEventListener<T> implements ApplicationListener<MessageEvent<T>> {
/**
* 使用 @Async 开启方法异步执行,value属性指定要使用的线程池
*/
@Async(value = "executor")
@Override
public void onApplicationEvent(MessageEvent<T> event) {
Integer type = event.getType();
T content = event.getContent();
// 发短信
if(type == 1){
log.info("模拟发送短信,短信内容为:{}", content);
}
// 发邮件
else if(type == 2){
log.info("模拟发送邮件,邮件内容为:{}", content);
}
// 站内信
else {
log.info("模拟发送站内信,站内信内容为:{}", content);
}
}
}