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


原始标题:Spring Application Context Events