駕馭 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 |