Spring Boot 启动完成的准确信号是 ApplicationReadyEvent,它在所有初始化(包括 Web 容器启动、端口监听就绪)完成后才触发,而 ContextRefreshedEvent 在 IOC 容器刷新完即发出,此时 Tomcat 可能尚未绑定端口,HTTP 请求会返回 503。

监听 Spring Boot 启动完成用 ApplicationReadyEvent,不是 ContextRefreshedEvent
Spring Boot 启动完成的准确信号是 ApplicationReadyEvent,它在所有初始化(包括 Web 容器启动、端口监听就绪)完成后才触发。而 ContextRefreshedEvent 在 IOC 容器刷新完就发了,此时 Tomcat 可能还没真正 bind 端口,HTTP 请求会 503 —— 很多监控/预热逻辑放这里会失败。
实操建议:
- 监听
ApplicationReadyEvent:确保所有自动配置、内嵌容器、Actuator 端点都已就绪 - 避免监听
ContextRefreshedEvent做 HTTP 调用或依赖外部服务的初始化 - 如果需要更早介入(比如修改 Bean 定义),用
ApplicationContextInitializer,但它不属事件机制
写一个 ApplicationListener 类要加 @Component 或手动注册
只实现 ApplicationListener<ApplicationReadyEvent> 接口不会自动生效,Spring 不会扫描纯接口实现类。必须显式让 Spring 管理这个监听器实例。
实操建议:
- 最简单:加上
@Component注解,Spring Boot 启动时自动发现 - 若需控制顺序(比如某些监听器必须先于其他执行),加
@Order(1)或实现Ordered - 若监听器依赖尚未初始化的 Bean(如
DataSource),注意ApplicationReadyEvent保证了所有@PostConstruct和InitializingBean.afterPropertiesSet()已执行完毕
示例:
@Component
@Order(1)
public class StartupListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("✅ Spring Boot 已完全启动");
}
}
用 @EventListener 更轻量,但要注意泛型擦除陷阱
@EventListener 是基于注解的替代方案,写法更简洁,底层仍走 Spring 事件机制。但它依赖泛型类型推断,而 Java 泛型运行时被擦除,所以不能写成 @EventListener ApplicationReadyEvent 这种裸类型。
实操建议:
- 必须写全泛型:用
@EventListener(ApplicationReadyEvent.class)显式指定事件类型 - 支持条件监听:
@EventListener(condition = "#event.source.server.port != 0"),适合多环境差异化逻辑 - 不推荐在同一个方法里监听多个事件(比如
@EventListener({ApplicationReadyEvent.class, ContextClosedEvent.class})),因为无法区分事件来源,也难做类型安全操作
示例:
@Component
public class AnnotatedStartupListener {
@EventListener(ApplicationReadyEvent.class)
public void onAppReady(ApplicationReadyEvent event) {
// event.getSource() 是 ConfigurableApplicationContext 实例
int port = ((ConfigurableApplicationContext) event.getSource())
.getEnvironment().getProperty("server.port", Integer.class, 8080);
System.out.println("Started on port: " + port);
}
}
异步监听启动事件?别直接用 @Async,小心上下文未准备好
给监听方法加 @Async 很容易引发 NoSuchBeanDefinitionException 或空指针——因为 @Async 的代理和线程池初始化可能晚于事件触发时机,导致事务、AOP、甚至 ApplicationContext 本身不可用。
实操建议:
- 真要异步执行耗时操作(如调用远程配置中心、预热缓存),在监听方法里手动提交到自定义线程池,而不是依赖
@Async - 自定义线程池必须声明为
@Bean并设setWaitForTasksToCompleteOnShutdown(true),否则 JVM 可能在异步任务结束前退出 - 不要在异步块里调用
@Transactional方法,传播行为不可靠;如有 DB 操作,改用JdbcTemplate直连或确保 DataSource 已完全初始化
@Order 或 Ordered 是唯一可控手段;而 ApplicationReadyEvent 的触发时机,取决于你是否启用了 Web 环境、是否用了响应式栈、甚至是否禁用了 Actuator —— 这些都会轻微偏移事件发出时刻。










