spring的事件监听
概述
对于 Spring 容器的一些事件,可以监听并且触发相应的方法
- Spring监听器是一种特殊的类,它们能帮助开发者监听 web 中特定的事件
比如 ServletContext, HttpSession, ServletRequest 的创建和销毁;
变量的创建、销毁等等。 - 当Web容器启动后,Spring的监听器会启动监听
首先,这种监听机制使用下面几种模式
- 观察者模式:主要是观察者模式
- 工厂模式(EventListenerFactory)
- 适配器模式(ApplicationListenerMethodAdapter),
事件监听方式
Spring 提供了两种事件监听方式:ApplicationListener 接口 和 @EventListener 注解。以下是它们的差异:
特性 | ApplicationListener 接口 | @EventListener 注解 |
---|---|---|
实现方式 | 需实现接口并重写方法 | 直接在方法上标注注解 |
事件类型绑定 | 通过泛型参数指定 | 通过方法参数或注解的 classes/value 指定 |
多事件监听 | 每个监听器只能监听一种事件 | 单个方法可监听多种事件(通过参数类型推断或显式指定) |
条件过滤 | 需在代码中手动判断 | 支持通过 condition 属性配置 SpEL 表达式 |
事务性事件 | 需自行实现事务逻辑 | 支持 @TransactionalEventListener 注解 |
代码侵入性 | 较高(需实现接口) | 较低(无侵入) |
(1) 优先使用 ApplicationListener 的场景
- 需要强类型事件绑定:明确监听特定类型的事件。
- 旧代码兼容:遗留系统已基于接口实现事件监听。
- 复杂事件处理逻辑:需在监听器中封装多步骤逻辑。
(2) 优先使用 @EventListener 的场景
- 简化代码:无需实现接口,直接通过注解绑定方法。
- 动态条件过滤:需要根据事件属性动态决定是否处理。
- 多事件监听:单个方法处理多种事件类型。
Spring内置的事件
Spring内置了的事件类型,分别在以下情况下触发:
- ContextRefreshedEvent:当
应用程序上下文被刷新时触发
。这个事件在ApplicationContext初始化或刷新时被发布,适用于执行初始化操作和启动后的后续处理
。例如,初始化缓存、预加载数据等。 - ContextStartedEvent:当
应用程序上下文启动
时触发。这个事件在调用ApplicationContext的start()方法时被发布,适用于在应用程序启动时执行特定的操作。例如,启动定时任务、启动异步消息处理等。 - ContextStoppedEvent:当
应用程序上下文停止时
触发。这个事件在调用ApplicationContext的stop()方法时被发布,适用于在应用程序停止时执行清理操作。例如,停止定时任务、关闭数据库连接等。 - ContextClosedEvent:当
应用程序上下文关闭时触发
。这个事件在调用ApplicationContext的close()方法时被发布,适用于在应用程序关闭前执行最后的清理工作。例如,释放资源、保存日志等。 - RequestHandledEvent:在Web应用程序中,
当一个HTTP请求处理完成后
触发。这个事件在Spring的DispatcherServlet处理完请求后被发布,适用于记录请求日志、处理统计数据等。 - ApplicationEvent:这是一个抽象的基类,可以
用于定义自定义的应用程序事件
。你可以创建自定义事件类,继承自ApplicationEvent,并定义适合你的应用场景的事件类型
。
@EventListener注解
使用示例
Spring中一个内置监听器的简单示例:当启动一个基于Spring的应用程序时,当应用程序上下文被刷新时
,ContextRefreshedEvent事件将被触发,然后MyContextRefreshedListener监听器的onApplicationEvent方法将被调用
- 加上@EventListener(ContextRefreshedEvent.class),
Spring会在加载这个类时,为其创建一个监听器
,这个监听器监听的事件类型是ContextRefreshedEvent
,当此事件发生时,将触发执行该方法methodA。 - 使用 @Component 注解将该类声明为一个Spring管理的组件,这样Spring会自动将其纳入到应用程序上下文中,并在适当的时候触发监听
- 我们可以在这个类中写上多个方法,每个方法通过注解监听着不同的事件类型,这样我们就仅需使用一个类,却构建了多个监听器
@Component public class MyListener {@EventListener(ContextRefreshedEvent.class)public void methodA(ContextRefreshedEvent event) {System.out.println("应用程序上下文已刷新");// 在这里可以执行一些初始化操作} }
注解参数说明
注解源码如下,有两个参数:
有两个参数:
- classes 参数 / classes 参数:
- 作用:指定监听的事件类型(支持多个事件类)。
用于指定要监听的事件类型。
可以指定一个或多个事件类型,以数组形式传递 - 如下示例:
// 监听单一事件 @EventListener(value = UserCreatedEvent.class) public void handleUserEvent(UserCreatedEvent event) { ... }// 监听多个事件 @EventListener(classes = {OrderPaidEvent.class, OrderCanceledEvent.class}) public void handleOrderEvents(ApplicationEvent event) { if (event instanceof OrderPaidEvent) { ... } }
- 作用:指定监听的事件类型(支持多个事件类)。
- condition 参数:
- 作用:通过SpEL表达式动态过滤事件,表达式可访问事件对象的属性(通过#root.event或直接引用)。
用于指定一个
SpEL 表达式作为条件
,只有当条件满足时,才会执行监听器方法。 - 示例如下:
// 示例中,方法 handleEvent() 只有当事件源为 source 时才会执行 @EventListener(condition = "#event.source == 'source'") public void handleEvent(Event event) {// 处理事件逻辑 }@EventListener(condition = "#root.event.user.age > 18") public void onUserUpdate(UserUpdateEvent event) {// 只有当user对象的age属性大于18时,才会执行这个方法 }@EventListener(condition = "#root.args[0].user.gender == '男'") public void onUserUpdate(UserUpdateEvent event) {// 只有当user对象的gender属性为男时,才会执行这个方法 }@EventListener(condition = "@userService.isAdmin(#event.user)") public void onUserUpdate(UserUpdateEvent event) {// 只有当userService Bean的isAdmin方法返回true时,才会执行这个方法 }
- 作用:通过SpEL表达式动态过滤事件,表达式可访问事件对象的属性(通过#root.event或直接引用)。
- 若value()和classes()没有设置值,则标识
@EventListener的方法必须有且只能有一个参数
。如上demo中,其实可以不使用参数,直接使用注解即可
@Component public class MyListener {@EventListenerpublic void methodA(ContextRefreshedEvent event) {System.out.println("应用程序上下文已刷新");// 在这里可以执行一些初始化操作} }
- id 参数:
- 作用:为监听器定义唯一ID,常用于事务性事件(需结合@TransactionalEventListener)。
- 示例
@TransactionalEventListener(id = "orderListener", phase = TransactionPhase.AFTER_COMMIT) public void handleOrderAfterCommit(OrderEvent event) { ... }
ApplicationListener
基本概念
作用:ApplicationListener 是 事件监听器的通用接口,定义了监听器处理事件的标准方式。
- 监听事件:通过泛型参数指定要监听的事件类型(如 ApplicationEvent 或其子类)。
- 处理事件:实现 onApplicationEvent 方法,编写事件处理逻辑
核心方法 onApplicationEvent
onApplicationEvent(E event)
- 作用:处理接收到的事件。
- 触发时机:当 Spring 上下文发布与监听器泛型类型匹配的事件时调用。
- 参数:事件对象(如 UserCreatedEvent)。
- 示例
@Override public void onApplicationEvent(UserCreatedEvent event) {// 从事件中获取数据String username = event.getUsername();// 执行业务逻辑(如发送邮件、记录日志)notificationService.sendWelcomeEmail(username); }
使用注意
未指定泛型类型:监听器未通过泛型绑定事件类型,导致监听所有事件。如下:
// 错误:会监听所有类型的 ApplicationEvent
public class MyListener implements ApplicationListener { ... }
需要明确指定泛型类型。如下
public class MyListener implements ApplicationListener<MyEvent> { ... }
事件处理阻塞主线程:在 onApplicationEvent 中执行耗时操作(如 IO、网络请求),导致主线程卡顿。可以使用 @Async 注解异步处理。如下
@Async
@Override
public void onApplicationEvent(UserCreatedEvent event) { ... }
使用ApplicationListener 示例
实现一个内置监听器的简单示例:当启动一个基于Spring的应用程序时,当应用程序上下文被刷新时,ContextRefreshedEvent事件将被触发
,然后MyContextRefreshedListener监听器的onApplicationEvent方法将被调用
- 实现了ApplicationListener接口,这意味着这个
监听器监听的事件类型是ContextRefreshedEvent
,并重写了 onApplicationEvent 方法。该方法在应用程序上下文被刷新时触发。 - 使用 @Component 注解将该监听器声明为一个Spring管理的组件,这样Spring会自动将其纳入到应用程序上下文中,并在适当的时候触发监听
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class MyContextRefreshedListener implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {System.out.println("应用程序上下文已刷新");// 在这里可以执行一些初始化操作}
}
自定义事件与监听器
自定义事件
// 事件A
public class CustomEventA extends ApplicationEvent {private String message;public CustomEventA(Object source, String message) {super(source);this.message = message;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}
// 事件B
public class CustomEventB extends ApplicationEvent {private String message;public CustomEventB(Object source, String message) {super(source);this.message = message;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}
构建监听(@EventListener注解)
@Component
public class MyListener {//@EventListener(CustomEventA.class)@EventListenerpublic void methodA(CustomEventA event) {System.out.println("========我监听到事件A了:" + event.getMessage());// 在这里可以执行一些其他操作}//@EventListener(CustomEventB.class)@EventListenerpublic void methodB(CustomEventB event) {System.out.println("========我监听到事件B了:" + event.getMessage());// 在这里可以执行一些其他操作}
}
发布事件
当容器启动后,调用接口,便会触发下述两个事件
import org.springframework.context.ApplicationContext;@Resource
private ApplicationContext applicationContext;@GetMapping("/publishEvent")
public void publishEvent() {CustomEventA eventA = new CustomEventA(applicationContext , "我是AAAA");CustomEventB eventB = new CustomEventB(applicationContext , "我是BBBB");applicationContext.publishEvent(eventA);applicationContext.publishEvent(eventB);
}
@EventListener常见错误使用
下述情况会导致失败:
监听方法的修饰符是private
;@EventListener private void methodB(CustomEventB event) {
- 事件类型不匹配:
监听方法是监听的CustomEventB
,但入参却是CustomEventA ;@EventListener(CustomEventB.class) private void methodB(CustomEventA event) {
事件监听容器还没有该事件的观察者,就会导致事件发布了,但是没有相应观察者进行监听
Spring监听器原理
整体原理
流程梳理
在Spring中,监听器实体全部放在ApplicationContext中,事件也是通过ApplicationContext来进行发布
通过ApplicationContext发布的事件
,其并不是自己进行事件的发布,而是引入了一个处理器—— EventMulticaster,直译就是事件多播器- 事件多播器:它负责在大量的监听器中,针对每一个要广播的事件,找到事件对应的监听器,然后调用该监听器的响应方法,图中就是调用了监听器1、3、6
只有在某类事件第一次广播时,EventMulticaster才会去做遍历所有监听器的事
,当它针对该类事件广播过一次后,就会把对应监听器保存起来了,最后会形成一个缓存Map,下一次就能直接找到这些监听器final Map<ListenerCacheKey, ListenerRetriever> retrieverCache = new ConcurrentHashMap<>(64);
关键角色
组件 | 作用 |
---|---|
ApplicationEvent | 事件基类,所有自定义事件必须继承它(或 PayloadApplicationEvent) |
ApplicationListener< E> | 监听器接口,泛型 < E> 指定监听的事件类型 |
ApplicationEventPublisher | 事件发布接口,由 ApplicationContext 实现,提供 publishEvent() 方法 |
ApplicationEventMulticaster | 事件多播器,负责将事件分发给匹配的监听器(默认实现为 SimpleApplicationEventMulticaster) |
组件协作流程:
- 发布事件:通过 ApplicationEventPublisher.publishEvent() 发布事件。
- 事件广播:ApplicationEventMulticaster 接收事件,遍历所有监听器。
- 筛选监听器:根据事件类型匹配对应的 ApplicationListener。
- 触发监听器:调用监听器的 onApplicationEvent(E event) 方法。
ApplicationListener接口
大致代码逻辑
spring 提供的EventListenerFactory有两个:
- DefaultEventListenerFactory:spring 默认的
- 能支持所有标记了@EventListener的方法
- 创建ApplicationListener的方法是createApplicationListener,它调用的是ApplicationListenerMethodAdapter的构造方法,
在ApplicationListenerMethodAdapter中,其中一堆的赋值操作
,从此处可以得出结论:a.被@EventListener标记的方法,参数最多只能有一个;b.方法能监听的事件可以有多个,由@EventListener指定
- 通过反射调用被@EventListener标记的方法
- 处理@EventListener方法的返回结果,在handleResult(…)方法里可以将返回的结果当作事件再发布出去
- TransactionalEventListenerFactory:处理事务监听的
- 只支持标记了 @TransactionalEventListener的方法;
- 这个注解标记了@EventListener,因此具有与@EventListener相同的功能
- 多了两个属性:phase()与fallbackExecution(),看来是用来控制事务的
- 创建的ApplicationListener实际类型为ApplicationListenerMethodTransactionalAdapter
- ApplicationListenerMethodTransactionalAdapter继承了ApplicationListenerMethodAdapter,其构造方法也是先调用ApplicationListenerMethodAdapter的构造方法,然后再给annotation赋值
- 该方法主要是监听事件
- 继承来自ApplicationListenerMethodAdapter的方法
- ApplicationListenerMethodTransactionalAdapter并没有重写supportsEventType(…)方法,因此也是使用ApplicationListenerMethodAdapter的supportsEventType(…)方法来判断事件的支持情况
- 在处理事件时,ApplicationListenerMethodTransactionalAdapter会进行事务相关的处理,
- 若是当前线程不存在事务,则判断@TransactinalEventListener的fallbackExecution属性是否是true。若是true,则表示若当前线程没有正在运行事务运行,该监听器也会监听事件发布,运行processEvent方法调用方法处理事件
- 只支持标记了 @TransactionalEventListener的方法;
原理总结
注册入口
Spring 容器启动时:通过 ApplicationContext 自动扫描所有实现了 ApplicationListener 接口的 Bean
。
底层机制:
- 在 Bean 初始化阶段,Spring 检测到 Bean 实现了 ApplicationListener 接口。
- 将该 Bean 注册到 ApplicationEventMulticaster 的监听器列表中。
- 关键源码(AbstractApplicationContext):
protected void registerListeners() {// 注册静态指定的监听器for (ApplicationListener<?> listener : getApplicationListeners()) {getApplicationEventMulticaster().addApplicationListener(listener);}// 注册 Bean 形式的监听器String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);for (String listenerBeanName : listenerBeanNames) {getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);}
}
事件类型匹配机制
泛型类型解析
:Spring 通过 泛型参数 确定 ApplicationListener 监听的事件类型。
- 示例:Spring 会解析 MyEvent 作为该监听器关注的事件类型。
public class MyListener implements ApplicationListener<MyEvent> { ... }
匹配逻辑
- 事件类型检查:判断当前事件是否是监听器泛型类型的实例(instanceof)。
- 继承支持:如果监听器监听的是父类事件(如 ApplicationEvent),则会接收到所有子类事件。
缓存机制
:Spring 会缓存监听器与事件类型的映射关系,避免每次事件发布时重复解析泛型。
事件广播与分发
事件发布入:调用 ApplicationContext.publishEvent() 时,实际委托给 ApplicationEventMulticaster:
public void publishEvent(ApplicationEvent event) {// 委托给多播器getApplicationEventMulticaster().multicastEvent(event);
}
多播器核心逻辑
:SimpleApplicationEventMulticaster 的 multicastEvent() 方法:
- 遍历所有监听器:检查是否支持当前事件类型。
- 同步/异步执行:根据配置决定是否使用线程池异步触发监听器。
@EventListener原理
大致代码逻辑
在Bean生成流程中,会对每个Bean都使用PostProcessor来进行加工
BeanFactoryPostProcessor的实现类EventListenerMethodProcessor会处理@EventListener注解
EventListenerMethodProcessor实现了两个接口
-
BeanFactoryPostProcessor:可以定制化BeanFactory的一些行为;
在postProcessBeanFactory()方法在中,从容器中获取了EventListenerFactory并赋值给了eventListenerFactories
。- 从这个地方可以看出来,EventListenerFactory有两个:
- DefaultEventListenerFactory:spring 默认的
- TransactionalEventListenerFactory:处理事务监听的
-
SmartInitializingSingleton:
处理单例bean的初始化操作,执行时机是在bean初始化完成之后
。- 在该实现里面的afterSingletonsInstantiated()方法里面:将@EventListener标记的方法转换成ApplicationListener对象
- 遍历这些bean,对每一个bean,如果是代理对象,则调用AutoProxyUtils.determineTargetClass(beanFactory, beanName)方法获取其目标类,我们知道注解是不能继承的,要获取@EventListener标记的方法,需要从目标类去获取;如果不是代理对象,则目标类就是bean对应的类;
- 其次,
调用processBean(beanName, type)方法进一步处理,如果标记了@EventListener,都会被找到,最终会把找到的所有方法放到一个Map中
遍历上述得到的map,将每个方法转换为ApplicationListener 对象,再添加到applicationContext的ApplicationListener中
- 在该实现里面的afterSingletonsInstantiated()方法里面:将@EventListener标记的方法转换成ApplicationListener对象
-
被 @EventListener 标记的方法之所以能对事件进行监听,是
因为 spring 将该方法包装成一个ApplicationListener添加到监听器
,之后这个监听器就跟实现了ApplicationListener接口的监听器一样能对事件进行监听了。
原理总结
@EventListener 的整个生命周期可以分为以下步骤:
- Bean 后处理阶段:Spring 容器启动时,通过 EventListenerMethodProcessor 扫描所有 Bean 中被 @EventListener 注解标记的方法。
- 生成监听器适配器:将每个注解方法包装成一个 ApplicationListenerMethodAdapter,这是一个 ApplicationListener 接口的实现类。
- 注册监听器:
- Spring 启动时,通过 EventListenerMethodProcessor 扫描所有 Bean 的方法,找到标记 @EventListener 的方法。
- 将这些方法包装成 ApplicationListenerMethodAdapter(实现了 ApplicationListener 接口),并注册到事件广播器 ApplicationEventMulticaster 中。
- 事件触发:当调用 ApplicationContext.publishEvent() 发布事件时,事件广播器会遍历所有监听器,找到匹配的监听方法并执行。
异步监听
使用 @EventListener注解标注事件被触发时执行的方法(默认是同步执行,异步需要额外配置)
当事件触发时,Spring Boot 事件监听器会在同一个线程中处理该事件。这种处理方式的好处是可以保证处理事件的顺序和一致性,但如果事件处理比较耗时,可能会阻塞主线程
配置方式有两种:
- 配置监听器异步执行
- 使用@Async 注解
配置监听器异步执行
探索
监听器最终是通过ApplicationEventMulticaster内部的实现来调用的
- 这个类默认有个实现类SimpleApplicationEventMulticaster,这个类是支持监听器异步调用的
- SimpleApplicationEventMulticaster中事件监听器的调用,最终会执行下面这个方法
- 在 executor 不为空时会使用线程池去执行 invokeListener(自定义监听事件)
- 所以:
我们只要定义一个SimpleApplicationEventMulticaster 的bean并设置他的taskExecutor就可以异步了
实现
配置便可以分为下述两个步骤
- 自定义事件广播器:SimpleApplicationEventMulticaster
- 线程池配置
// 第一步:自定义事件广播器
@Configuration
public class ApplicationEventAsyncConfig {@Resourceprivate ThreadPoolTaskExecutor myExecutor;@Beanpublic ApplicationEventMulticaster applicationEventMulticaster() { //@1//创建一个事件广播器SimpleApplicationEventMulticaster result = new SimpleApplicationEventMulticaster();//设置异步执行器,来完成异步执行监听事件这样会导致所有的监听器都异步执行result.setTaskExecutor(myExecutor);return result;}
}// 第二步:线程池配置
@Configuration
public class ThreadPoolConfig {@Bean("myExecutor")public Executor taskExecutor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();//设置线程池参数信息taskExecutor.setCorePoolSize(8);taskExecutor.setMaxPoolSize(20);taskExecutor.setQueueCapacity(50);taskExecutor.setKeepAliveSeconds(60);taskExecutor.setThreadNamePrefix("myExecutor--");taskExecutor.setWaitForTasksToCompleteOnShutdown(true);// 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。taskExecutor.setAwaitTerminationSeconds(60);//修改拒绝策略为使用当前线程执行taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//初始化线程池taskExecutor.initialize();return taskExecutor;}
}
使用@Async 注解
探索
@Async默认情况下用的是SimpleAsyncTaskExecutor线程池,该线程池不是真正意义上的线程池
使用此线程池无法实现线程重用,每次调用都会新建一条线程
。
若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误
实现
四步便可以实现:(如果想要简单一点的话,直接使用第一步和第二步即可)
- 在事件监听器上加上 @Async 注解来实现异步处理事件
- 项目启动类上添加@EnableAsync注解
- 配置线程池:也可以
通过@Async("线程池bean名称“)来决定使用哪个线程池
,如果有多个线程池,但是在@Async注解里面没有指定的话,会默认加载第一个配置的线程池 - 配置异常处理(子线程执行异常无法被主线程捕获)
定义SpringAsyncConfiguration 类实现AsyncConfigurer 接口去定义执行的线程池和异常处理类(只能处理无返回值的异常)
第一步:在事件监听器上加上 @Async 注解来实现异步处理事件
//1. 在事件监听器上加上 @Async 注解来实现异步处理事件
@Component
@Async
public class MyListener {//@EventListener(CustomEventA.class)@EventListenerpublic void methodA(CustomEventA event) {System.out.println("========我监听到事件A了:" + event.getMessage());// 在这里可以执行一些其他操作}//@EventListener(CustomEventB.class)@EventListenerpublic void methodB(CustomEventB event) {System.out.println("========我监听到事件B了:" + event.getMessage());// 在这里可以执行一些其他操作}
}
第二步:项目启动类上添加@EnableAsync注解
// 2. 项目启动类上添加@EnableAsync注解
@EnableAsync
@SpringBootApplication
public class EventApplication {public static void main(String[] args) {SpringApplication.run(EventApplication.class, args);}
}
第三步: 和上述使用“配置监听器异步执行”差不多,这里就不赘述了
第四步:配置异常处理(子线程执行异常无法被主线程捕获)
// 第四步:
@Configuration
@Slf4j
public class SpringAsyncConfiguration implements AsyncConfigurer {@Resourceprivate ThreadPoolTaskExecutor myExecutor;@Overridepublic Executor getAsyncExecutor() {return myExecutor;}@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return new SpringAsyncExceptionHandler();}class SpringAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {@Overridepublic void handleUncaughtException(Throwable throwable, Method method, Object... obj) {log.error("Exception occurs in async method:{} ,err:{}",method.getName(), throwable.getMessage());}}
}
监听器执行顺序
同步执行
使用 @Order(int 类型的值value) 注解可以定义监听器的执行顺序,value越小优先级越高。
异步执行
异步情况下无法控制执行顺序。即便是使用了@Older()注解