spring 中监听系统初始化事件首选 contextrefreshedevent(bean 初始化完成后)或 applicationreadyevent(应用完全就绪后),前者需过滤主容器,后者天然单次触发且时机更晚;应避免空指针、异步未初始化等问题,简单场景优先用 @postconstruct 或 commandlinerunner。

Spring 使用 @EventListener 监听系统初始化事件,核心是监听 ContextRefreshedEvent 或 ApplicationReadyEvent —— 前者在上下文刷新完成时触发(包括 Bean 初始化完毕、配置加载完成),后者在应用完全启动就绪(如 Web 服务已监听端口)后才发出。
监听 ContextRefreshedEvent:Bean 就绪后执行
这是最常用的方式,适用于需要在所有单例 Bean 初始化完成后执行的逻辑,比如预热缓存、校验配置、初始化静态数据等。
- 确保方法所在类是 Spring 管理的 Bean(加
@Component或其他 stereotype 注解) - 方法参数类型必须为具体事件类型,Spring 会自动匹配并注入
- 一个应用中可能触发多次
ContextRefreshedEvent(例如有父子容器时),可用applicationContext.getParent() == null过滤主容器事件
@Component
public class InitListener {
@EventListener
public void onContextRefresh(ContextRefreshedEvent event) {
if (event.getApplicationContext().getParent() == null) {
System.out.println("主应用上下文已刷新,可安全使用所有 Bean");
// 执行初始化逻辑
}
}
}
监听 ApplicationReadyEvent:应用真正就绪后执行
适合依赖外部服务(如数据库连接池已建立、HTTP 服务已启动)或需触发 HTTP 健康检查、发送启动通知等场景。它比 ContextRefreshedEvent 触发更晚,且只发生一次。
- 无需判断父容器,天然保证是主应用的最终就绪信号
- 此时
@PostConstruct、InitializingBean.afterPropertiesSet()、以及所有@EventListener(ContextRefreshedEvent)都已完成 - 注意:若应用启动失败(如端口被占),该事件不会发出
@Component
public class ReadyListener {
@EventListener
public void onApplicationReady(ApplicationReadyEvent event) {
System.out.println("应用已启动完成,Web 服务已就绪");
// 启动定时任务、注册到注册中心、发 Slack 通知等
}
}
避免常见陷阱
初始化逻辑容易因执行时机不当导致空指针或状态不一致,需特别注意:
- 不要在监听方法里直接调用尚未初始化的 Bean(尤其是被
@Lazy或原型作用域修饰的) - 异步执行耗时操作时,建议用
@Async并确保线程池已初始化(推荐在ApplicationReadyEvent中触发) - 避免在监听器中修改 Bean 定义或重新注册 BeanDefinition,可能引发上下文状态混乱
- 若需顺序控制(如 A 初始化完再执行 B),优先用
@DependsOn或SmartLifecycle,而非依赖事件触发顺序
替代方案对比:什么情况下不用 @EventListener
对于简单、确定的初始化动作,Spring 提供了更轻量或更可控的选择:
-
@PostConstruct:适合单个 Bean 内部的初始化,不跨 Bean 协作 -
InitializingBean.afterPropertiesSet():同上,但侵入性强,已不推荐 -
CommandLineRunner/ApplicationRunner:语义明确,专为启动后执行设计,支持排序(@Order),且天然只在主应用上下文中运行一次 -
SmartLifecycle:适合需要控制启动/停止生命周期、依赖顺序或延迟启动的组件











