1. Spring Boot啟動事件概述
1.1 什么是Spring Boot啟動事件
在Spring Boot的應用生命周期中,從main
方法執行到應用完全就緒,期間會發生一系列事件(Event)。這些事件由Spring Boot框架在特定時間點觸發,用于通知系統當前運行階段的狀態,并允許我們在這些時間點插入自定義邏輯。
可以把Spring Boot的啟動過程想象成一個舞臺劇:
燈光亮起(ApplicationStartingEvent)
舞臺布景準備好(ApplicationEnvironmentPreparedEvent)
演員到位(ApplicationPreparedEvent)
正式開演(ApplicationStartedEvent)
演出完成(ApplicationReadyEvent)
演出失敗(ApplicationFailedEvent)
這些事件的主要作用包括:
生命周期鉤子(Lifecycle Hooks):在特定啟動階段執行邏輯,如加載外部配置、初始化緩存等。
解耦:監聽器與事件觸發點解耦,方便擴展和維護。
可觀測性:配合日志或監控,可以跟蹤啟動進度和狀態。
1.2 事件在Spring Boot中的作用
Spring Boot事件機制主要解決了兩個問題:
啟動過程的擴展:
例如在Spring Context初始化前,我們就能通過事件拿到Environment
,從而動態修改配置。運行時狀態監聽:
例如在應用就緒后,立即啟動一個異步任務,或在啟動失敗時發送報警信息。
常見應用場景:
在
ApplicationStartingEvent
階段設置日志系統參數。在
ApplicationEnvironmentPreparedEvent
階段加載云端配置文件。在
ApplicationReadyEvent
階段預熱數據或啟動定時任務。在
ApplicationFailedEvent
階段上報啟動失敗的原因。
2. Spring Boot啟動事件分類
2.1 核心事件列表
Spring Boot 啟動過程中會觸發一系列標準事件,這些事件是按照應用生命周期的先后順序觸發的。核心事件包括:
事件類名 | 觸發時機 | 特點 |
---|---|---|
ApplicationStartingEvent | SpringApplication.run() 剛開始執行時,且在創建 ApplicationContext 之前 | 最早觸發的事件,可以在這里做一些全局初始化工作,如修改 Banner、初始化日志系統 |
ApplicationEnvironmentPreparedEvent | 環境變量 (Environment ) 準備好,但 ApplicationContext 還未創建 | 可以在這里讀取配置文件、動態修改配置 |
ApplicationContextInitializedEvent | ApplicationContext 已創建但還未進行 Bean 加載 | 適合做一些基于容器的初始化工作 |
ApplicationPreparedEvent | ApplicationContext 已刷新(Bean 定義已加載),但還未調用 refresh() 完成 | 可以在這里對 Bean 做調整 |
ApplicationStartedEvent | 應用已啟動,但 CommandLineRunner 和 ApplicationRunner 還未執行 | 表示應用啟動階段已完成,馬上要進入業務邏輯階段 |
ApplicationReadyEvent | 所有 Runner 執行完畢,應用已完全就緒 | 常用于啟動定時任務、發通知、預熱緩存 |
ApplicationFailedEvent | 啟動過程中發生異常時觸發 | 常用于記錄錯誤日志、報警、清理資源 |
2.2 事件觸發的完整生命周期
下面是事件觸發的時間線,可以幫助理解它們的先后順序:
ApplicationStartingEvent
SpringApplication 剛開始運行。
ApplicationEnvironmentPreparedEvent
環境(Environment)已準備好。
ApplicationContextInitializedEvent
ApplicationContext 已初始化但未加載 Bean。
ApplicationPreparedEvent
ApplicationContext 已準備好 Bean 定義,但未刷新。
ApplicationStartedEvent
應用啟動完成,Runner 未執行。
ApplicationReadyEvent
應用完全就緒,Runner 已執行。
ApplicationFailedEvent
啟動失敗時觸發(任何階段出錯都會觸發)。
💡 可以將其記為:
Start → Env → ContextInit → Prepared → Started → Ready → Failed
2.3 事件與ApplicationContext的關系
ApplicationContext
是 Spring 容器的核心,很多事件與它的生命周期緊密相關:
創建前:
ApplicationStartingEvent
和ApplicationEnvironmentPreparedEvent
發生在容器創建前,這時還沒有 Bean 信息。創建中:
ApplicationContextInitializedEvent
表示容器已實例化,但 Bean 還沒加載。準備就緒:
ApplicationPreparedEvent
表示 Bean 定義已加載,可以對 Bean 做最后的調整。運行階段:
ApplicationStartedEvent
、ApplicationReadyEvent
發生在容器完全啟動后,可以安全地訪問 Bean。異常階段:
ApplicationFailedEvent
可以拿到異常和容器狀態,方便做錯誤處理。
示例代碼:打印啟動事件順序
import org.springframework.boot.context.event.*;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;@Component
public class StartupEventLogger implements ApplicationListener<ApplicationEvent> {@Overridepublic void onApplicationEvent(ApplicationEvent event) {System.out.println("觸發事件: " + event.getClass().getSimpleName());}
}
運行 Spring Boot 應用時,你會在控制臺看到事件觸發的順序,從而直觀了解生命周期。
3. 事件監聽方式詳解
Spring Boot 支持多種方式監聽啟動事件,主要有三種常用手段:
@EventListener
注解(簡單優雅,推薦大多數場景)實現
ApplicationListener
接口(更傳統、可精細控制)自定義
SpringApplicationRunListener
(啟動最早階段可用)
3.1 使用 @EventListener 注解
原理
@EventListener
是 Spring 4.2 引入的事件監聽方式,基于反射,方法簽名中聲明的事件類型會被自動匹配。
它最大的好處是:不需要實現接口,代碼更簡潔。
使用步驟:
在 Spring Bean 中編寫一個普通方法。
方法上添加
@EventListener
注解。方法參數為需要監聽的事件類型。
示例:監聽 ApplicationReadyEvent
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;@Component
public class ReadyEventListener {@EventListenerpublic void handleReady(ApplicationReadyEvent event) {System.out.println("應用已就緒,可以開始執行任務!");// 比如預熱緩存}
}
優點:
代碼簡潔,靈活。
支持條件匹配(
@EventListener(condition = "")
)。
缺點:
無法在 Spring 容器完全創建前使用(因為需要依賴 Bean)。
3.2 實現 ApplicationListener 接口
原理
ApplicationListener
是 Spring 事件機制的傳統接口,早于 @EventListener
出現。
通過實現該接口,可以監聽指定事件類型,支持類型安全和較高性能。
使用步驟:
實現
ApplicationListener<T>
接口,T 為事件類型。將監聽器注冊為 Spring Bean(
@Component
或手動注冊)。
示例:監聽 ApplicationStartedEvent
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;@Component
public class StartedEventListener implements ApplicationListener<ApplicationStartedEvent> {@Overridepublic void onApplicationEvent(ApplicationStartedEvent event) {System.out.println("應用啟動完成,Runner 還未執行!");}
}
優點:
類型安全(編譯期即可檢查事件類型)。
性能好(無反射調用)。
缺點:
代碼稍顯冗長。
只能監聽單一類型(要監聽多個事件需多實現)。
3.3 自定義 SpringApplicationRunListener
原理
SpringApplicationRunListener
是 Spring Boot 提供的一個擴展點,用于在SpringApplication.run() 的各個階段執行邏輯。
它的生命周期比普通事件監聽器更早,甚至可以在 ApplicationContext 創建前執行邏輯。
使用步驟:
實現
SpringApplicationRunListener
接口。在
META-INF/spring.factories
文件中注冊。在接口方法中編寫啟動階段邏輯。
示例:記錄啟動流程
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;public class MyRunListener implements SpringApplicationRunListener {public MyRunListener(SpringApplication application, String[] args) {// 必須有這個構造器}@Overridepublic void starting() {System.out.println("【RunListener】應用啟動中...");}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) {System.out.println("【RunListener】環境已準備好");}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {System.out.println("【RunListener】上下文已創建");}@Overridepublic void started(ConfigurableApplicationContext context) {System.out.println("【RunListener】應用已啟動");}@Overridepublic void ready(ConfigurableApplicationContext context) {System.out.println("【RunListener】應用已就緒");}@Overridepublic void failed(ConfigurableApplicationContext context, Throwable exception) {System.out.println("【RunListener】啟動失敗:" + exception.getMessage());}
}
META-INF/spring.factories
文件注冊:
org.springframework.boot.SpringApplicationRunListener=\
com.example.listener.MyRunListener
優點:
生命周期最早,可以做環境準備、日志初始化等。
不依賴 Spring 容器。
缺點:
配置復雜(需在
spring.factories
注冊)。主要用于框架級擴展,不推薦業務代碼中頻繁使用。
4. 事件的實際應用場景
Spring Boot 啟動事件的真正價值,在于它可以幫我們在恰當的時間點執行關鍵任務,從而讓系統啟動更加平滑、可控。
下面我們結合常見業務場景,給出可以直接運行的示例。
4.1 初始化外部資源(如數據庫連接)
場景
有些外部資源(如數據庫連接池、消息隊列客戶端、第三方 API)需要在應用啟動早期就準備好。
如果等到業務邏輯調用時再初始化,可能會導致首個請求延遲較高。
選擇事件
ApplicationStartedEvent
因為此時容器已準備好,可以安全獲取 Bean,但業務邏輯還未開始執行。
示例:初始化數據庫連接池
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;@Component
public class DatabaseInitListener implements ApplicationListener<ApplicationStartedEvent> {@Overridepublic void onApplicationEvent(ApplicationStartedEvent event) {System.out.println("初始化數據庫連接池...");// 模擬初始化try {Thread.sleep(2000);System.out.println("數據庫連接池初始化完成!");} catch (InterruptedException e) {e.printStackTrace();}}
}
4.2 動態加載配置文件
場景
有時我們需要在應用啟動前,從遠程配置中心(如 Nacos、Apollo)拉取最新配置。
這必須發生在 Spring 環境 (Environment
) 準備好之后,但在容器創建前。
選擇事件
ApplicationEnvironmentPreparedEvent
此時可以安全讀取/修改Environment
。
示例:加載遠程配置
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;@Component
public class RemoteConfigListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {@Overridepublic void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {ConfigurableEnvironment env = event.getEnvironment();System.out.println("從遠程配置中心加載配置...");// 模擬加載env.getSystemProperties().put("custom.config.source", "remote");}
}
4.3 預熱緩存數據
場景
在一些高并發系統中,首次請求往往需要加載大量數據。
為了減少冷啟動延遲,我們可以在應用就緒 (ApplicationReadyEvent
) 時提前加載緩存。
選擇事件
ApplicationReadyEvent
此時所有 Bean 已創建,Runner 已執行,系統處于穩定可用狀態。
示例:緩存預熱
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;@Component
public class CachePreheatListener {@EventListenerpublic void onReady(ApplicationReadyEvent event) {System.out.println("應用就緒,開始預熱緩存...");// 模擬緩存預熱try {Thread.sleep(1000);System.out.println("緩存預熱完成!");} catch (InterruptedException e) {e.printStackTrace();}}
}
4.4 處理啟動失敗事件
場景
在生產環境中,啟動失敗可能意味著業務中斷,需要第一時間報警或回滾操作。
選擇事件
ApplicationFailedEvent
任何階段發生異常都會觸發。
示例:啟動失敗報警
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;@Component
public class StartupFailedListener implements ApplicationListener<ApplicationFailedEvent> {@Overridepublic void onApplicationEvent(ApplicationFailedEvent event) {System.err.println("應用啟動失敗,原因:" + event.getException().getMessage());// 模擬發送告警郵件}
}
5. 事件監聽的最佳實踐
事件監聽是一個很靈活的工具,但如果使用不當,可能會導致啟動變慢、邏輯混亂,甚至事件丟失。
下面從三個方面總結最佳實踐。
5.1 事件執行順序的控制
有時我們需要確保多個監聽器按指定順序執行,比如:
先加載配置,再初始化數據庫
先連接第三方服務,再啟動業務邏輯
Spring 提供了兩種方式來控制事件監聽器的執行順序:
方式一:@Order
注解
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;@Component
public class FirstReadyTask {@EventListener@Order(1) // 數字越小,優先級越高public void run(ApplicationReadyEvent event) {System.out.println("先執行任務A");}
}@Component
public class SecondReadyTask {@EventListener@Order(2)public void run(ApplicationReadyEvent event) {System.out.println("再執行任務B");}
}
方式二:實現 SmartApplicationListener
接口
SmartApplicationListener
提供了 getOrder()
方法,適合需要動態控制順序的場景。
5.2 異常處理與日志記錄
如果監聽器拋出未捕獲的異常,會影響啟動流程,甚至中斷應用。
建議:
捕獲并記錄異常,不要讓它向外拋。
對啟動非關鍵任務,異常時記錄日志但不阻斷啟動。
對關鍵任務(如加載核心配置),異常時可直接退出啟動,防止系統處于錯誤狀態。
示例:異常處理模板
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;@Component
public class SafeStartupListener implements ApplicationListener<ApplicationStartedEvent> {@Overridepublic void onApplicationEvent(ApplicationStartedEvent event) {try {// 執行關鍵邏輯System.out.println("執行關鍵啟動任務");} catch (Exception e) {// 記錄錯誤System.err.println("啟動任務失敗:" + e.getMessage());// 選擇:中斷啟動// System.exit(1);}}
}
5.3 性能優化建議
在高并發、啟動頻繁的系統(如微服務集群)中,啟動事件的執行效率很重要。
優化建議:
避免長耗時操作
如果必須執行耗時任務(如加載大文件、調用慢API),應考慮放到異步線程中執行:import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component;@Component public class AsyncCachePreheat {@Async@EventListenerpublic void onReady(ApplicationReadyEvent event) {System.out.println("異步預熱緩存...");} }
減少外部依賴的啟動阻塞
例如遠程配置中心不可用時,可以加載本地備份配置,避免啟動失敗。使用條件監聽
@EventListener
支持condition
屬性,只在滿足條件時執行監聽器: