1. 简介
Spring 框架提供了一套基于 ApplicationContext
的事件机制,允许我们在不同 Bean 之间进行松耦合的通信 ✅。通过监听特定事件,我们可以在应用生命周期的关键节点执行自定义逻辑。
比如,你可能希望在应用上下文完全启动后发送一条通知,或者在关闭前清理资源——这些都可以通过事件机制优雅实现。
📌 提示:本文聚焦于 Spring 内置事件的使用。关于如何发布自定义事件,请参考我们之前的教程 Spring Events。
虽然 Spring 内部大量使用这些事件,但开发者手动监听的情况相对较少。不过一旦踩过坑(比如误判应用是否真正就绪),就会发现它们非常关键。
2. 标准上下文事件
Spring 提供了一系列内置事件,覆盖了 ApplicationContext
的核心生命周期阶段。掌握这些事件,等于掌握了应用的“心跳节奏”。
2.1. ContextRefreshedEvent
- 触发时机:
ApplicationContext
初始化完成或刷新时 - 关键点:
- 初次加载、调用
refresh()
方法都会触发 - 表示所有单例 Bean 已创建完毕,容器已准备就绪 ✅
- 可多次触发(只要 context 未关闭)
- 初次加载、调用
@EventListener
public void onContextRefreshed(ContextRefreshedEvent event) {
System.out.println("✅ 应用上下文已刷新,所有Bean加载完成");
}
⚠️ 注意:Web 环境下,DispatcherServlet 对应的子容器也会触发此事件,需根据 event.getApplicationContext()
判断是否为主容器。
2.2. ContextStartedEvent
- 触发时机:手动调用
context.start()
时 - 使用场景:
- 显式启动被停止的组件(如消息监听器)
- 配合
Lifecycle
接口实现可控制的启动逻辑
@EventListener
public void handleContextStart(ContextStartedEvent event) {
System.out.println("▶️ 应用已启动(start)");
}
✅ start()
是显式调用,不会自动触发,适合需要手动控制启停的场景。
2.3. ContextStoppedEvent
- 触发时机:调用
context.stop()
时 - 特点:
- 容器进入暂停状态,但未销毁
- 可通过
start()
重新激活 ❗
@EventListener
public void onContextStopped(ContextStoppedEvent event) {
System.out.println("⏸️ 应用已停止,可重启");
}
2.4. ContextClosedEvent
- 触发时机:调用
context.close()
时 - 终极状态:
- 容器生命周期结束
- 所有单例 Bean 被销毁
- 无法重启 ❌
@EventListener
public void onContextClosed(ContextClosedEvent event) {
System.out.println("🛑 应用上下文已关闭,资源释放");
}
⚠️
close()
通常由 JVM Shutdown Hook 触发,适合做最后的资源清理。
3. 使用 @EventListener
监听事件
从 Spring 4.2 开始,@EventListener
成为监听事件的首选方式——简单粗暴,无需实现接口。
基础用法
方法参数即事件类型,Spring 自动完成匹配:
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
System.out.println("应用刷新完成,执行初始化任务");
}
✅ 优势:
- 零配置:
@EnableTransactionManagement
或<context:annotation-driven/>
已包含支持 - 类型安全:编译期检查事件类型
- 支持返回值:返回非 null 值会发布新事件(形成事件链)
3.1. 监听多个事件
使用 classes
属性监听多个事件类型:
@EventListener(classes = {
ContextStartedEvent.class,
ContextStoppedEvent.class
})
public void handleLifecycleEvents() {
System.out.println("🔄 收到上下文启停事件");
}
等效于注册多个监听器,但更简洁。
💡 提示:若事件处理逻辑差异大,建议拆分为多个方法,便于维护。
4. 传统方式:实现 ApplicationListener
在 Spring 4.2 之前,必须显式实现 ApplicationListener
接口:
@Component
public class LegacyEventListener
implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("旧式监听器收到刷新事件");
}
}
虽然现在不推荐,但在维护老项目时仍会遇到。
5. 总结
事件 | 触发方法 | 是否可重启 |
---|---|---|
ContextRefreshedEvent |
refresh() |
✅ 多次 |
ContextStartedEvent |
start() |
✅ |
ContextStoppedEvent |
stop() |
✅ |
ContextClosedEvent |
close() |
❌ |
- ✅ 优先使用
@EventListener
,代码更简洁 - ⚠️ 注意
ContextRefreshedEvent
在父子容器中的重复触发问题 - 🛠️ 关键清理逻辑放在
ContextClosedEvent
中,确保执行
所有示例代码已托管至 GitHub:https://github.com/tech-tutorial/spring-core-events