驾驭 Spring Boot 事件机制:8 个内置事件 + 自定义扩展实战
驾驭 Spring Boot 事件机制:8 个内置事件 + 自定义扩展实战
在 Spring Boot 应用的完整生命周期中,框架为我们预埋了 8 个关键事件(Application-level & Context-level)。 理解并善用这些事件,可以在“不侵入框架、不修改源码”的前提下,注入个性化初始化、监控、清理逻辑。 本文将带你从 0 到 1 掌握事件机制,并给出可直接落地的代码模板。
一、为什么需要事件机制?
场景 | 传统做法 | 事件机制优势 |
---|---|---|
启动时加载字典缓存 | CommandLineRunner | 无侵入、可插拔、可排序 |
优雅停机 | @PreDestroy | 与 Spring 生命周期同步,确保资源释放顺序 |
多模块解耦 | 直接调用 | 发布-订阅,模块间零依赖 |
二、Spring Boot 8 大内置事件一览
事件 | 触发阶段 | 典型用途 | 监听器注册方式 |
---|---|---|---|
ApplicationStartingEvent | run() 刚被调用,日志系统尚未初始化 | 极早期检查、初始化日志桥接 | SpringApplication.addListeners(...) |
ApplicationEnvironmentPreparedEvent | Environment 已就绪,但 BeanDefinition 尚未加载 | 动态修改配置源、激活 Profile | 同上 |
ApplicationContextInitializedEvent | ApplicationContext 已创建,但尚未 refresh | 注册 BeanFactoryPostProcessor | 同上 |
ApplicationPreparedEvent | BeanDefinition 已加载,Environment 可用 | 读取配置、校验必备属性 | 同上 |
ContextRefreshedEvent | refresh() 完成,所有单例已实例化 | 缓存预热、注册监控 | @Component |
ServletWebServerInitializedEvent | 内嵌容器端口已打开 | 获取运行时端口、注册服务发现 | @Component |
ApplicationStartedEvent | 容器已启动,所有 CommandLineRunner 已执行 | 发送启动成功指标 | @Component |
ApplicationReadyEvent | 同上,额外保证所有应用初始化器已完成 | 开启流量、发送通知 | @Component |
Spring Boot 2.x 之后新增
ApplicationStartingEvent
、ApplicationStartedEvent
等,旧版只有 5 个核心事件。
三、实战:监听 4 个高频事件
1. 启动早期动态注入配置
public class EarlyEnvInjector implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {@Overridepublic void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {ConfigurableEnvironment env = event.getEnvironment();// 模拟从 Apollo/Nacos 拉取最新配置Map<String, Object> override = Map.of("spring.datasource.url", "jdbc:mysql://newHost/dev");env.getPropertySources().addFirst(new MapPropertySource("dynamic", override));}
}
注册方式(在 main
方法里):
SpringApplication app = new SpringApplication(DemoApp.class);
app.addListeners(new EarlyEnvInjector());
app.run(args);
2. 容器刷新后预热缓存
@Component
public class CacheWarmer implements ApplicationListener<ContextRefreshedEvent> {@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {if (event.getApplicationContext().getParent() == null) { // 防止重复执行DictCache.loadAll();}}
}
3. 优雅停机前释放资源
@Component
public class GracefulShutdown implements ApplicationListener<ContextClosedEvent> {private final ExecutorService pool = Executors.newFixedThreadPool(10);@Overridepublic void onApplicationEvent(ContextClosedEvent event) {pool.shutdown();try {if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {pool.shutdownNow();}} catch (InterruptedException e) {pool.shutdownNow();}}
}
4. 启动完毕发送监控告警
@Component
public class StartupReporter implements ApplicationListener<ApplicationReadyEvent> {@Overridepublic void onApplicationEvent(ApplicationReadyEvent event) {InetAddress host = InetAddress.getLocalHost();String port = event.getApplicationContext().getEnvironment().getProperty("local.server.port");DingTalk.send("✅ 服务启动完成: " + host.getHostAddress() + ":" + port);}
}
四、扩展:自定义业务事件
1. 定义领域事件
public class OrderPaidEvent extends ApplicationEvent {private final Long orderId;private final BigDecimal amount;public OrderPaidEvent(Object source, Long orderId, BigDecimal amount) {super(source);this.orderId = orderId;this.amount = amount;}// getters ...
}
2. 发布事件
@Service
@RequiredArgsConstructor
public class OrderService {private final ApplicationEventPublisher publisher;public void pay(Long orderId) {// 业务逻辑...publisher.publishEvent(new OrderPaidEvent(this, orderId, BigDecimal.valueOf(99)));}
}
3. 多监听器异步消费
@Component
public class InvoiceGenerator {@EventListener@Async("invoiceTaskExecutor") // 线程池隔离public void onOrderPaid(OrderPaidEvent event) {// 生成电子发票...}
}
五、最佳实践清单
- 顺序控制:使用
@Order
或实现Ordered
接口。 - 线程安全:早期事件(如
ApplicationStartingEvent
)发布时,Bean 尚未实例化,此时注册逻辑需避免依赖 IOC 容器。 - 条件化监听:
@ConditionalOnProperty
或Environment
判断,避免在测试环境触发线上逻辑。 - 异步场景:
@Async
+ 自定义线程池,防止阻塞主流程。 - 可观测性:通过 Micrometer 记录事件处理耗时,及时发现慢监听器。
六、小结
目标 | 推荐事件 |
---|---|
动态修改配置 | ApplicationEnvironmentPreparedEvent |
容器初始化后一次性任务 | ContextRefreshedEvent |
优雅停机 | ContextClosedEvent |
服务启动成功通知 | ApplicationReadyEvent |
业务解耦 | 自定义 ApplicationEvent |