驾驭 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> {
@Override
public 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> {
@Override
public 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);
@Override
public 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> {
@Override
public 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 |