Servlet 容器啟動時對 web.xml 的解析與初始化順序直接決定了應用上下文構建、過濾器鏈組裝、Servlet 實例化的時序關係。本文基於 Servlet Spec 3.0+、Tomcat 實現源碼、實戰排查經驗,系統闡述「context-param → listener → filter → servlet」的標準加載順序,以及同類元素內部映射順序對運行時行為的決定性影響。
1. context-param → ServletContext 初始化參數,最先可用
2. listener → ServletContextListener.contextInitialized() 回調
3. filter → Filter.init(),按 順序構建鏈
4. servlet → Servlet.init(),按 load-on-startup 值升序 (正數優先,負數/無則首次請求時)
核心結論:加載順序與 web.xml 中元素出現的物理順序無關,由規範固定。但同類元素的 映射順序 (filter-mapping / servlet-mapping) 決定運行時攔截/調用順序。
ServletContext.getInitParameter() 讀取。contextConfigLocation、日誌配置路徑、功能開關。<listener> 聲明順序實例化並調用 contextInitialized(SCE)。ContextLoaderListener (Spring) — 初始化 Root WebApplicationContextRequestContextListener — 綁定 Request 上下文到線程HttpSessionEventPublisher — 發布 Session 事件給 Spring SecurityQuartzInitializerListener、數據源預熱、緩存預熱init(FilterConfig),按 <filter-mapping> 順序組裝鏈。doFilter(req, res, chain) 中 chain.doFilter() 前 = 前置處理,後 = 後置處理。load-on-startup 規則:
- 正整數:容器啟動時按數值升序初始化 (1 最先);相同值按聲明順序。
- 0 或負數 / 缺省:容器延遲到首次請求映射到該 Servlet 時初始化。
DispatcherServlet 通常設 load-on-startup: 1 確保最先就緒。init-param → 構建 WebApplicationContext (子上下文) → 掃描 Controller、HandlerMapping 等組件。Tomcat 啟動報錯:NullPointerException at QuartzInitializerListener.contextInitialized,提示 DataSource 為 null。
<!-- 錯誤配置順序 -->
<listener>
<listener-class>org.quartz.ee.servlet.QuartzInitializerListener</listener-class>
</listener>
<servlet>
<servlet-name>dataSourceInit</servlet-name>
<servlet-class>com.app.DataSourceInitServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
Listener 階段執行時,DataSourceInitServlet 尚未初始化 (Servlet 階段才跑),DataSource 未綁定到 JNDI/ServletContext,Quartz 拿不到連接池。
ServletContextListener) 或 ServletContextAttributeListener,確保在 Quartz Listener 之前完成。@PostConstruct / SmartLifecycle / ApplicationRunner 統一生命週期管理,容器級加載順序問題消失。| 元素 | 聲明順序影響 | 映射順序影響 | 運行時行為 |
|---|---|---|---|
| filter | 僅影響 init() 調用順序 | 決定 doFilter() 調用順序 | 請求 → Filter1 → Filter2 → ... → Servlet |
| servlet | 同 load-on-startup 值時決定 init() 順序 | 決定 URL 映射優先級 (精確匹配 > 路徑匹配 > 擴展匹配 > 默認) | 同一 URL 可能匹配多個 servlet-mapping,按映射聲明順序取第一個 |
| listener | 決定 contextInitialized 調用順序 | 無映射概念 | 順序執行,異常中斷後續 |
ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean 程序化註冊,順序由 @Order / Ordered 控制。@SpringBootApplication + web.xml 共存,但 以程序化註冊為準,web.xml 內同名組件會被忽略或報錯。ServletContainerInitializer (SpringServletContainerInitializer) 掃描 @HandlesTypes 註解類,替代 web.xml 完成自動配置。context-param 放文件最前,語義清晰。load-on-startup: 1,其他業務 Servlet ≥ 2 或延遲加載。WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()) 延遲獲取,或遷移到 Spring 生命週期。mvn dependency:tree 檢查 Servlet API 版本衝突 (2.5 vs 3.1 vs 4.0)。web.xml 加載順序是 規格固定的「context-param → listener → filter → servlet」,同類映射順序決定 運行時攔截/調用鏈。理解這一機制能快速定位啟動階段依賴缺失、過濾器順序錯亂、Servlet 映射衝突等經典問題。現代 Spring Boot 項目應優先使用程序化註冊與自動配置,將順序控制交給 @Order 與條件裝配,徹底告別 XML 維護負擔。