今天我们聚焦 SpringBoot 核心底层机制之一------事件驱动模型:ApplicationEvent 与监听器。
很多同学对它的认知,只停留在"监听 SpringBoot 启动"的浅层用法,甚至觉得"日常开发用不上"。但实际上,它是 Spring 全家桶中最强大的解耦神器,也是面试高频考点,更是 SpringBoot 自身底层实现的核心依赖(比如自动配置、启动流程、上下文刷新,都靠事件机制驱动)。
日常开发中,无论是用户注册后的通知推送、订单支付后的后续处理,还是系统初始化、日志收集、监控告警,用事件机制都能让代码变得简洁、解耦、可扩展,还能轻松实现异步处理,避免阻塞主线程。
一、事件机制的核心本质(观察者模式)
Spring 事件机制,本质是标准的观察者模式(发布-订阅模式) ,核心目的是解耦发布者与订阅者,让两者之间没有直接依赖,仅通过"事件"进行通信。
1. 观察者模式三要素
Spring 事件机制完美对应观察者模式的三个核心角色,搞懂这三个角色,就掌握了事件机制的核心逻辑:
-
• 事件(Event) :事件本身,是发布者与监听器之间通信的载体,包含事件相关的所有数据(如用户注册事件,会包含用户ID、手机号等信息)。Spring 中所有事件都必须继承
ApplicationEvent类。 -
• 发布者(Publisher) :事件的发起者,负责创建事件、并将事件发布到 Spring 容器中。Spring 中通过
ApplicationEventPublisher接口实现事件发布。 -
• 监听器(Listener) :事件的订阅者,负责监听指定类型的事件,当事件被发布时,自动执行对应的处理逻辑。Spring 中所有监听器都需要实现
ApplicationListener接口,或通过注解标注。
2. 事件机制的核心流程
发布者 → 创建事件 → 发布事件(通过 ApplicationEventPublisher) → Spring 容器转发事件 → 所有监听该事件的监听器,自动执行处理逻辑
✅ 核心优势:发布者只需要关注"发布事件",不用关心谁会监听、如何处理;监听器只需要关注"监听事件",不用关心事件是谁发布的、从哪来的。两者完全解耦,后续修改任何一方的逻辑,都不会影响另一方。
3. 为什么要用事件机制?
举一个最常见的实战场景:用户注册。
传统写法(耦合严重):
go
// 用户注册服务
@Service
public class UserService {
// 依赖通知服务、日志服务、积分服务
@Autowired
private NoticeService noticeService;
@Autowired
private LogService logService;
@Autowired
private PointService pointService;
// 注册方法:耦合了通知、日志、积分等无关逻辑
public void register(User user) {
// 1. 保存用户(核心逻辑)
saveUser(user);
// 2. 发送注册通知(非核心逻辑)
noticeService.sendRegisterNotice(user);
// 3. 记录注册日志(非核心逻辑)
logService.recordRegisterLog(user);
// 4. 赠送注册积分(非核心逻辑)
pointService.giveRegisterPoint(user);
}
}
问题:核心的"用户注册"逻辑,与"通知、日志、积分"等非核心逻辑强耦合,后续要修改通知方式(如从短信改成邮件)、新增注册后逻辑(如绑定会员),都需要修改 UserService 代码,违反"开闭原则",维护成本极高。
事件机制写法(彻底解耦):
go
// 1. 定义用户注册事件(载体)
public class UserRegisterEvent extends ApplicationEvent {
private User user;
// 构造方法:必须调用父类构造器,传入事件源(发布者)
public UserRegisterEvent(Object source, User user) {
super(source);
this.user = user;
}
// getter 方法(供监听器获取事件数据)
public User getUser() { return user; }
}
// 2. 发布者(用户服务):只关注核心逻辑,发布事件即可
@Service
public class UserService implements ApplicationEventPublisherAware {
// 注入事件发布器
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void register(User user) {
// 1. 保存用户(核心逻辑)
saveUser(user);
// 2. 发布用户注册事件(不关心后续处理)
publisher.publishEvent(new UserRegisterEvent(this, user));
}
}
// 3. 监听器1:处理注册通知(解耦,无需依赖 UserService)
@Component
public class RegisterNoticeListener implements ApplicationListener<UserRegisterEvent> {
@Autowired
private NoticeService noticeService;
// 事件触发时执行
@Override
public void onApplicationEvent(UserRegisterEvent event) {
User user = event.getUser();
noticeService.sendRegisterNotice(user);
}
}
// 4. 监听器2:处理注册日志(解耦)
@Component
public class RegisterLogListener implements ApplicationListener<UserRegisterEvent> {
@Autowired
private LogService logService;
@Override
public void onApplicationEvent(UserRegisterEvent event) {
User user = event.getUser();
logService.recordRegisterLog(user);
}
}
// 5. 监听器3:处理注册积分(解耦)
@Component
public class RegisterPointListener implements ApplicationListener<UserRegisterEvent> {
@Autowired
private PointService pointService;
@Override
public void onApplicationEvent(UserRegisterEvent event) {
User user = event.getUser();
pointService.giveRegisterPoint(user);
}
}
✅ 优势:UserService 只负责核心的用户注册逻辑,后续新增/修改注册后处理逻辑,只需新增/修改监听器,无需修改 UserService 代码,彻底解耦,扩展性极强。
二、核心 API 详解
Spring 事件机制的核心,就是三个核心 API:ApplicationEvent(事件)、ApplicationListener(监听器)、ApplicationEventPublisher(发布者)。下面逐个解析,结合源码理解底层逻辑。
1. ApplicationEvent:事件的顶层抽象
ApplicationEvent 是 Spring 中所有事件的顶层抽象类,位于 org.springframework.context 包下,所有自定义事件都必须继承它。
核心源码
go
public abstract class ApplicationEvent extends EventObject {
// 事件发生的时间戳(自动赋值)
private final long timestamp;
// 构造方法:必须传入事件源(source),即事件的发布者
public ApplicationEvent(Object source) {
super(source);
this.timestamp = System.currentTimeMillis();
}
// 获取事件发生时间
public final long getTimestamp() {
return this.timestamp;
}
}
核心要点:
-
• 继承自 JDK 的
EventObject,该类维护了一个source字段,用于存储事件源(发布者); -
• 必须重写构造方法,传入事件源(source),父类会自动保存事件源;
-
• 内置
timestamp字段,自动记录事件发生的时间戳,无需手动赋值; -
• 自定义事件时,只需继承
ApplicationEvent,并添加自身需要的业务字段(如用户信息、订单信息)和 getter 方法。
自定义事件
go
import org.springframework.context.ApplicationEvent;
import lombok.Data;
import lombok.EqualsAndHashCode;
// 自定义事件:用户注册事件
@Data
@EqualsAndHashCode(callSuper = true) // 继承父类的 equals 和 hashCode 方法
public class UserRegisterEvent extends ApplicationEvent {
// 自定义业务字段:用户信息
private User user;
// 自定义业务字段:注册渠道(如APP、网页、小程序)
private String registerChannel;
// 构造方法:必须调用 super(source),传入事件源
public UserRegisterEvent(Object source, User user, String registerChannel) {
super(source);
this.user = user;
this.registerChannel = registerChannel;
}
// 无需手动写 getter/setter,@Data 注解自动生成
}
// 用户实体类(简化)
@Data
public class User {
private Long id;
private String username;
private String phone;
private String email;
}
2. ApplicationListener:监听器的顶层接口
ApplicationListener 是 Spring 中所有监听器的顶层接口,用于定义"事件触发时的处理逻辑",所有监听器都需要实现该接口(或通过注解替代)。
核心源码
go
@FunctionalInterface // 函数式接口,只有一个抽象方法
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
// 事件触发时,自动执行该方法(核心方法)
void onApplicationEvent(E event);
}
核心要点:
-
• 是一个函数式接口,只有一个抽象方法
onApplicationEvent(E event),该方法会在事件被发布时自动调用; -
• 泛型
E必须是ApplicationEvent的子类,表示该监听器只监听指定类型的事件(如ApplicationListener<UserRegisterEvent>只监听用户注册事件); -
• 监听器必须被 Spring 管理(添加
@Component、@Service等注解),否则 Spring 容器无法识别,无法触发事件处理。
3. ApplicationEventPublisher:事件发布器
ApplicationEventPublisher 是 Spring 提供的事件发布接口,用于发布事件,所有事件都必须通过该接口发布,Spring 容器会自动将事件转发给对应的监听器。
核心源码
go
public interface ApplicationEventPublisher {
// 发布事件(核心方法)
void publishEvent(ApplicationEvent event);
// 重载方法:支持发布非 ApplicationEvent 类型的事件(Spring 4.2+ 新增)
default void publishEvent(Object event) {
publishEvent(new PayloadApplicationEvent<>(this, event));
}
}
事件发布的两种方式
日常开发中,发布事件有两种常用方式,推荐第二种,更规范、更灵活。
方式1:实现 ApplicationEventPublisherAware 接口
通过实现 ApplicationEventPublisherAware 接口,Spring 会自动注入 ApplicationEventPublisher 对象,无需手动 @Autowired。
go
@Service
public class UserService implements ApplicationEventPublisherAware {
// 事件发布器(Spring 自动注入)
private ApplicationEventPublisher publisher;
// 实现接口方法,接收 Spring 注入的发布器
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
// 发布事件
public void register(User user, String registerChannel) {
// 1. 核心业务逻辑:保存用户
saveUser(user);
// 2. 发布事件(传入自定义事件对象)
publisher.publishEvent(new UserRegisterEvent(this, user, registerChannel));
}
private void saveUser(User user) {
// 模拟保存用户逻辑
System.out.println("保存用户:" + user.getUsername());
}
}
方式2:直接 @Autowired 注入 ApplicationEventPublisher
简单直接,适合不需要实现接口的场景,本质和方式1一致。
go
@Service
public class OrderService {
// 直接注入事件发布器
@Autowired
private ApplicationEventPublisher publisher;
// 发布订单支付事件
public void pay(Order order) {
// 1. 核心业务逻辑:订单支付
payOrder(order);
// 2. 发布事件
publisher.publishEvent(new OrderPayEvent(this, order));
}
private void payOrder(Order order) {
System.out.println("订单支付成功:" + order.getOrderNo());
}
}
4. 补充:ApplicationEventMulticaster(事件广播器)
很多同学会疑惑:发布者发布事件后,Spring 是如何将事件转发给所有监听器的?核心就是 ApplicationEventMulticaster(事件广播器)。
它是 Spring 事件机制的"中间件",负责接收发布者发布的事件,然后广播给所有监听该事件的监听器。Spring 容器默认会自动创建一个 SimpleApplicationEventMulticaster 实例,作为默认的事件广播器。
核心逻辑:发布者 → ApplicationEventPublisher → ApplicationEventMulticaster → 所有对应监听器
无需手动配置,Spring 自动管理,了解即可,后续异步处理会用到它。
三、4种监听器实现方式
Spring 提供了 4 种监听器实现方式,适用于不同场景,下面逐个讲解,配套完整代码,按需选择使用。
方式1:实现 ApplicationListener 接口
最基础的方式,适用于所有场景,尤其是需要自定义复杂处理逻辑的情况,前面的示例用的就是这种方式。
go
// 监听器:处理用户注册事件(实现 ApplicationListener 接口)
@Component // 必须被 Spring 管理
public class UserRegisterListener implements ApplicationListener<UserRegisterEvent> {
// 事件触发时执行的逻辑
@Override
public void onApplicationEvent(UserRegisterEvent event) {
// 1. 获取事件中的业务数据
User user = event.getUser();
String registerChannel = event.getRegisterChannel();
// 2. 处理业务逻辑(如发送通知)
System.out.println("监听器1(接口方式):用户 " + user.getUsername() +
" 通过 " + registerChannel + " 渠道注册,发送短信通知...");
}
}
✅ 特点:通用、灵活,可处理复杂逻辑,支持泛型指定监听的事件类型,避免接收无关事件。
方式2:使用 @EventListener 注解
Spring 4.2+ 新增注解方式,无需实现接口,只需在方法上添加 @EventListener 注解,指定监听的事件类型,简洁高效,是日常开发中最推荐的方式。
go
@Component
public class UserRegisterAnnotationListener {
// 注解指定监听的事件类型,方法参数为事件对象
@EventListener(UserRegisterEvent.class)
public void handleUserRegisterEvent(UserRegisterEvent event) {
User user = event.getUser();
String registerChannel = event.getRegisterChannel();
System.out.println("监听器2(注解方式):用户 " + user.getUsername() +
" 通过 " + registerChannel + " 渠道注册,记录注册日志...");
}
// 可选:一个方法可监听多个事件(用数组指定)
@EventListener({UserRegisterEvent.class, OrderPayEvent.class})
public void handleMultiEvent(ApplicationEvent event) {
if (event instanceof UserRegisterEvent) {
// 处理用户注册事件
} else if (event instanceof OrderPayEvent) {
// 处理订单支付事件
}
}
}
✅ 特点:简洁、高效,无需实现接口,可监听多个事件,推荐日常开发使用。
⚠️ 注意:方法参数必须是事件对象(或事件的 payload),否则 Spring 无法识别监听的事件类型。
方式3:使用 @TransactionalEventListener 注解(事务绑定)
日常开发中,经常需要"事务提交后,再执行事件处理逻辑"(如订单支付事务提交后,再发送支付通知,避免事务未提交,通知已发送,出现数据不一致)。此时就需要用 @TransactionalEventListener 注解,它是 @EventListener 的子类,支持事务绑定。
go
@Component
public class UserRegisterTransactionalListener {
// 事务提交后,再执行事件处理(核心:phase = TransactionPhase.AFTER_COMMIT)
@TransactionalEventListener(
value = UserRegisterEvent.class,
phase = TransactionPhase.AFTER_COMMIT // 事务提交后触发
)
public void handleUserRegisterAfterCommit(UserRegisterEvent event) {
User user = event.getUser();
// 事务提交后,发送注册邮件(确保用户已保存到数据库)
System.out.println("监听器3(事务绑定):用户 " + user.getUsername() +
" 注册事务已提交,发送邮件通知...");
}
// 可选:其他事务阶段
@TransactionalEventListener(
value = UserRegisterEvent.class,
phase = TransactionPhase.BEFORE_COMMIT // 事务提交前触发
)
public void handleBeforeCommit(UserRegisterEvent event) {
// 事务提交前执行(如校验数据)
}
}
✅ 核心参数:phase(事务阶段),可选值:
-
•
BEFORE_COMMIT:事务提交前触发; -
•
AFTER_COMMIT:事务提交后触发(最常用); -
•
AFTER_ROLLBACK:事务回滚后触发; -
•
AFTER_COMPLETION:事务完成后触发(无论提交还是回滚)。
⚠️ 注意:该注解只有在"发布事件的方法被 @Transactional 标注"时才生效,否则事务阶段不生效。
方式4:实现 SmartApplicationListener 接口(支持监听顺序)
前面三种方式,监听器的执行顺序是不确定的(Spring 按Bean加载顺序执行)。如果需要指定多个监听器的执行顺序(如先记录日志,再发送通知,最后赠送积分),就需要实现SmartApplicationListener 接口,它是ApplicationListener 的子接口,支持指定监听顺序。
go
// 监听器4:记录日志(顺序1,最先执行)
@Component
public class RegisterLogSmartListener implements SmartApplicationListener {
// 1. 指定监听的事件类型
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
// 只监听 UserRegisterEvent 事件
return UserRegisterEvent.class.isAssignableFrom(eventType);
}
// 2. 指定监听的事件源类型(可选,可省略)
@Override
public boolean supportsSourceType(Class<?> sourceType) {
// 只监听 UserService 发布的事件
return UserService.class.isAssignableFrom(sourceType);
}
// 3. 指定执行顺序(值越小,优先级越高)
@Override
public int getOrder() {
return 1; // 顺序1,最先执行
}
// 4. 事件处理逻辑
@Override
public void onApplicationEvent(ApplicationEvent event) {
UserRegisterEvent registerEvent = (UserRegisterEvent) event;
User user = registerEvent.getUser();
System.out.println("监听器4(顺序1):用户 " + user.getUsername() + " 注册,记录日志...");
}
}
// 监听器5:发送通知(顺序2,中间执行)
@Component
public class RegisterNoticeSmartListener implements SmartApplicationListener {
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return UserRegisterEvent.class.isAssignableFrom(eventType);
}
@Override
public int getOrder() {
return 2; // 顺序2,中间执行
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
UserRegisterEvent registerEvent = (UserRegisterEvent) event;
User user = registerEvent.getUser();
System.out.println("监听器5(顺序2):用户 " + user.getUsername() + " 注册,发送通知...");
}
}
// 监听器6:赠送积分(顺序3,最后执行)
@Component
public class RegisterPointSmartListener implements SmartApplicationListener {
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return UserRegisterEvent.class.isAssignableFrom(eventType);
}
@Override
public int getOrder() {
return 3; // 顺序3,最后执行
}
@Override
public void onApplicationEvent(ApplicationEvent event) {
UserRegisterEvent registerEvent = (UserRegisterEvent) event;
User user = registerEvent.getUser();
System.out.println("监听器6(顺序3):用户 " + user.getUsername() + " 注册,赠送积分...");
}
}
✅ 执行效果:启动项目,调用 UserService 的 register 方法,监听器会按 order 值从小到大执行(1→2→3),顺序完全可控。
⚠️ 注意:getOrder() 方法返回值越小,优先级越高;如果多个监听器 order 值相同,执行顺序不确定。
四、同步 vs 异步:监听器的执行方式
Spring 事件机制默认是同步执行的:发布者发布事件后,会阻塞主线程,等待所有监听器执行完成,才会继续执行后续代码。但很多场景下,我们需要异步执行(如发送通知、记录日志,不阻塞核心业务),此时就需要配置异步监听器。
1. 同步执行(默认)
核心特点
-
• 发布者与监听器在同一个线程中执行,主线程会阻塞,直到所有监听器执行完成;
-
• 事务一致:如果发布者在事务中,监听器也会在同一个事务中(可通过 @TransactionalEventListener 控制);
-
• 适用于:监听器处理逻辑简单、耗时短,且需要和发布者事务一致的场景。
同步执行示例(默认行为)
go
@Service
public class UserService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
// 同步发布事件
public void register(User user) {
System.out.println("主线程开始:" + Thread.currentThread().getName());
publisher.publishEvent(new UserRegisterEvent(this, user, "APP"));
System.out.println("主线程结束:" + Thread.currentThread().getName());
}
}
// 监听器(同步执行)
@Component
public class UserRegisterListener implements ApplicationListener<UserRegisterEvent> {
@Override
public void onApplicationEvent(UserRegisterEvent event) {
try {
// 模拟耗时操作(如发送通知)
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("监听器执行:" + Thread.currentThread().getName());
}
}
执行结果(同步阻塞)
go
主线程开始:main
监听器执行:main(阻塞2秒)
主线程结束:main
问题:监听器耗时2秒,主线程会阻塞2秒,影响核心业务的响应速度。
2. 异步执行
通过配置 @EnableAsync 注解和@Async 注解,实现监听器异步执行,不阻塞主线程。
异步配置步骤
go
// 1. 启动类添加 @EnableAsync,开启异步支持
@SpringBootApplication
@EnableAsync // 开启异步
public class EventDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EventDemoApplication.class, args);
}
}
// 2. 监听器方法添加 @Async 注解,标记为异步执行
@Component
public class UserRegisterAsyncListener {
// @Async 注解:标记该方法异步执行
@Async
@EventListener(UserRegisterEvent.class)
public void handleUserRegisterAsync(UserRegisterEvent event) {
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步监听器执行:" + Thread.currentThread().getName());
}
}
// 3. 发布者(无需修改,和同步发布一致)
@Service
public class UserService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
public void register(User user) {
System.out.println("主线程开始:" + Thread.currentThread().getName());
publisher.publishEvent(new UserRegisterEvent(this, user, "APP"));
System.out.println("主线程结束:" + Thread.currentThread().getName());
}
}
执行结果(异步非阻塞)
go
主线程开始:main
主线程结束:main(立即执行,不阻塞)
异步监听器执行:task-1(2秒后执行,在子线程中)
✅ 优势:主线程无需等待监听器执行完成,直接继续执行后续逻辑,提升核心业务响应速度;监听器在子线程中执行,不影响主线程。
异步执行的进阶配置(自定义线程池)
默认情况下,Spring 会使用默认的线程池(SimpleAsyncTaskExecutor),该线程池每次都会创建新线程,性能较差。实际开发中,建议自定义线程池,优化异步性能。
go
// 自定义异步线程池配置
@Configuration
@EnableAsync
public class AsyncConfig {
// 自定义线程池
@Bean(name = "eventAsyncPool")
public Executor eventAsyncPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数
executor.setCorePoolSize(5);
// 最大线程数
executor.setMaxPoolSize(10);
// 队列容量
executor.setQueueCapacity(20);
// 线程空闲时间(秒)
executor.setKeepAliveSeconds(60);
// 线程名称前缀
executor.setThreadNamePrefix("event-async-");
// 拒绝策略(队列满时,直接抛出异常)
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
// 初始化线程池
executor.initialize();
return executor;
}
}
// 监听器指定使用自定义线程池
@Component
public class UserRegisterAsyncListener {
// 指定线程池名称,使用自定义线程池
@Async("eventAsyncPool")
@EventListener(UserRegisterEvent.class)
public void handleUserRegisterAsync(UserRegisterEvent event) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步监听器执行:" + Thread.currentThread().getName()); // 输出 event-async-1
}
}
同步 vs 异步 对比
| 对比维度 | 同步执行(默认) | 异步执行(@EnableAsync + @Async) |
|---|---|---|
| 线程 | 发布者与监听器同线程 | 监听器在子线程中执行 |
| 阻塞性 | 主线程阻塞,等待监听器完成 | 主线程不阻塞,直接继续执行 |
| 事务一致性 | 与发布者在同一事务中 | 事务独立(需单独控制) |
| 适用场景 | 监听器耗时短、需事务一致 | 监听器耗时长(如通知、日志)、不影响核心业务 |
| 性能 | 无需线程切换,简单高效 | 需线程切换,需配置线程池优化 |
五、Spring 事件机制的执行流程
前面我们讲了用法,现在深入底层,梳理 Spring 事件机制的完整执行流程,结合源码片段,让你彻底理解"事件是如何从发布到被监听器处理"的。
完整执行流程
-
- 发布事件 :发布者调用
ApplicationEventPublisher.publishEvent()方法,传入事件对象;
- 发布事件 :发布者调用
-
- 转发事件 :
ApplicationEventPublisher会将事件转发给ApplicationEventMulticaster(事件广播器);
- 转发事件 :
-
- 获取监听器 :
ApplicationEventMulticaster会从 Spring 容器中,获取所有监听该事件类型的监听器(通过supportsEventType()方法判断);
- 获取监听器 :
-
- 排序监听器 :如果监听器实现了
SmartApplicationListener,会按getOrder()方法的返回值排序(值越小,优先级越高);
- 排序监听器 :如果监听器实现了
-
- 执行监听器:
-
• 同步执行:在当前线程中,依次调用所有监听器的
onApplicationEvent()方法; -
• 异步执行:将监听器任务提交到指定的线程池,在子线程中执行。
-
- 执行完成:所有监听器执行完成后,事件处理结束(同步执行时,主线程继续执行;异步执行时,主线程早已结束)。
核心源码片段
go
// 1. ApplicationEventPublisher 的 publishEvent 方法(AbstractApplicationContext 实现)
@Override
public void publishEvent(ApplicationEvent event) {
publishEvent(event, null);
}
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
// 省略无关代码...
// 2. 获取事件广播器,转发事件
ApplicationEventMulticaster multicaster = getApplicationEventMulticaster();
try {
// 3. 广播事件,执行监听器
multicaster.multicastEvent(event, eventType);
} catch (Exception ex) {
// 异常处理...
}
}
// 4. ApplicationEventMulticaster 的 multicastEvent 方法(SimpleApplicationEventMulticaster 实现)
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : resolveDefaultEventType(event);
// 5. 获取所有监听该事件的监听器
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
// 6. 异步执行判断:如果有异步线程池,异步执行;否则同步执行
Executor executor = getTaskExecutor();
if (executor != null) {
// 异步执行:提交到线程池
executor.execute(() -> invokeListener(listener, event));
} else {
// 同步执行:直接调用监听器方法
invokeListener(listener, event);
}
}
}
// 7. 调用监听器的 onApplicationEvent 方法
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
} catch (Exception ex) {
// 异常处理...
}
}
✅ 核心总结:事件发布后,由事件广播器(ApplicationEventMulticaster)负责获取、排序监听器,并根据是否有异步线程池,决定同步或异步执行监听器。
六、SpringBoot 内置事件
SpringBoot 自身的启动流程、自动配置、上下文刷新等功能,都是通过事件机制驱动的。SpringBoot 提供了多个内置事件,我们可以监听这些事件,实现自定义逻辑(如项目启动后初始化缓存、启动完成后发送通知)。
常用内置事件(按触发顺序排列)
| 内置事件 | 触发时机 | 核心用途 |
|---|---|---|
| ApplicationStartingEvent | SpringBoot 应用启动开始时(最早触发,上下文未初始化) | 初始化一些启动前的配置(如系统参数初始化) |
| ApplicationEnvironmentPreparedEvent | 环境配置(Environment)准备完成后,上下文未创建 | 修改环境配置、添加自定义配置源 |
| ApplicationContextInitializedEvent | 应用上下文(ApplicationContext)初始化完成,Bean 未加载 | 上下文相关的初始化操作 |
| ApplicationPreparedEvent | 应用上下文准备完成,Bean 已加载,未刷新 | Bean 加载后的自定义操作 |
| ContextRefreshedEvent | 应用上下文刷新完成(所有 Bean 已初始化) | 初始化缓存、加载字典数据、启动定时任务 |
| ApplicationStartedEvent | SpringBoot 应用启动完成(所有 Bean 已初始化,服务未就绪) | 启动完成后的通知、监控告警 |
| ApplicationReadyEvent | 应用启动完成,服务已就绪,可接收请求 | 发送应用启动成功通知、执行初始化任务 |
| ApplicationFailedEvent | 应用启动失败时 | 记录启动失败日志、发送告警通知、清理资源 |
监听 SpringBoot 启动完成事件
最常用的场景:监听 ApplicationReadyEvent,在应用启动完成、可接收请求后,执行初始化操作(如加载缓存、发送启动通知)。
go
@Component
public class ApplicationReadyListener {
// 监听应用启动完成事件(服务已就绪)
@EventListener(ApplicationReadyEvent.class)
public void handleApplicationReady() {
// 1. 初始化缓存(如加载用户字典、商品分类)
initCache();
// 2. 发送启动成功通知(如钉钉、企业微信)
sendStartNotice();
// 3. 打印启动完成日志
System.out.println("SpringBoot 应用启动完成,服务已就绪,可接收请求!");
}
private void initCache() {
System.out.println("初始化缓存...");
// 模拟缓存初始化逻辑
}
private void sendStartNotice() {
System.out.println("发送应用启动成功通知...");
// 模拟发送通知逻辑
}
}
七、总结
SpringBoot 事件机制的核心是观察者模式,核心价值在于"解耦"------彻底分离发布者与订阅者,让核心业务逻辑与非核心逻辑(通知、日志、缓存等)相互独立,降低维护成本、提升代码扩展性。
-
• 核心三要素:事件(ApplicationEvent 子类)、发布者(ApplicationEventPublisher)、监听器(ApplicationListener 或注解方式);
-
• 4种监听器用法:接口实现、@EventListener、@TransactionalEventListener、SmartApplicationListener;
执行方式可灵活选择:同步执行适配短耗时、事务一致场景,异步执行(结合自定义线程池)适配长耗时、不阻塞核心业务场景;底层依赖事件广播器(ApplicationEventMulticaster)实现事件转发,SpringBoot 内置事件则支撑了自身启动、自动配置等核心流程。掌握这些知识点,既能在开发中灵活运用事件机制解耦代码,也能从容应对面试中关于事件机制的底层提问,真正做到学以致用。