文章目錄
- 一、雙容器架構:MVC容器與根容器的關系
- 二、啟動全流程解析
- 1. 啟動流程全景圖
- 2. 初始化根容器(Root WebApplicationContext)
- 2.1 Tomcat 中啟動入口源碼解析
- 2.2 Spring 根上下文啟動源碼解析
- 3. 初始化 MVC 容器(DispatcherServlet 的 WebApplicationContext)
- 3.1 Tomcat 中啟動入口源碼解析
- 3.2 Spring MVC上下文啟動源碼解析
- 1. ★MVC容器初始化入口:HttpServletBean
- 2. 創建 MVC 容器:FrameworkServlet
- 3. 核心邏輯:initWebApplicationContext()
- 4. 創建MVC子容器:createWebApplicationContext()
- 5. 配置并刷新容器:configureAndRefreshWebApplicationContext()
- 6. 初始化 MVC 組件:DispatcherServlet 的 onRefresh()
- 3.3 核心啟動流程
- 4. 關鍵設計解析
- 1. 父子容器設計的優勢
- 2. 設計意義與價值
- 三、調試技巧
- 四、總結
- 擴展
- DispatcherServlet Diagram
- Tomcat 中的完整調用棧
- Servlet 3.0+ 無配置啟動(Java Config)
在Java Web開發中,理解Spring MVC如何與Tomcat等Web容器協同工作是掌握企業級應用開發的關鍵。本文將深入解析Spring MVC容器在Web容器中的啟動過程,揭示父子容器協作的奧秘。
在上一篇中詳細介紹了 Spring IOC容器在web容器中的啟動過程,這篇進一步了解下Spring MVC容器(即 Web 應用上下文)是如何在web容器(如 Tomcat、Jetty)中啟動并生效的。在 Spring MVC 中,MVC 容器(即 DispatcherServlet
的 Web 應用上下文)的初始化過程是一個精密的協作機制。以下是詳細的啟動流程和關鍵代碼調用:
一、雙容器架構:MVC容器與根容器的關系
Spring MVC采用父子容器設計,實現業務層與Web層的關注點分離:
- 根容器:由
ContextLoaderListener
創建,管理業務層和數據層Bean - MVC容器:由
DispatcherServlet
創建,管理Web層組件 - 依賴規則:子容器可訪問父容器的Bean,反之則不行
二、啟動全流程解析
1. 啟動流程全景圖
Web 容器啟動:
- Web 容器(如 Tomcat)啟動時,會加載 web.xml(或 Servlet 3.0+ 的注解配置)。
- 容器根據配置初始化
ServletContext
(全局上下文),作為整個 Web 應用的共享空間。 - 在
ServletContext
基礎上展開Spring Web 容器的一系列啟動初始化
2. 初始化根容器(Root WebApplicationContext)
2.1 Tomcat 中啟動入口源碼解析
源碼入口:StandardContext.startInternal()
核心點:上面 Tomcat 源碼中 listener.contextInitialized(event)
方法會執行到 Spring ContextLoaderListener.contextInitialized()
方法, 從而初始化Spring Web 根上下文(IOC容器),建立起在Web環境中Spring IOC容器。
2.2 Spring 根上下文啟動源碼解析
源碼入口:ContextLoaderListener.contextInitialized()
[提示]:
詳細解析過程可查閱: Spring IOC容器在web容器中的啟動過程
3. 初始化 MVC 容器(DispatcherServlet 的 WebApplicationContext)
Tomcat 等 Web 容器(Servlet 容器)啟動時調用 Servlet
的 init()
方法是一個由 Java Servlet 規范定義的標準過程,其細節如下:
3.1 Tomcat 中啟動入口源碼解析
源碼入口:StandardContext.startInternal()
Servlet 加載機制: StandardContext.loadOnStartup()
核心代碼:StandardWrapper.loadServlet()
核心點:上面的 Tomcat 源碼中GenericServlet.init()
方法實際會調用到 Spring DispatcherServlet.load()
方法(DispatcherServlet的繼承鏈:DispatcherServlet → FrameworkServlet → HttpServletBean→ HttpServlet→ GenericServlet
),從而初始化Spring MVC 子上下文(Web IOC容器),建立起在Web環境中Spring MVC架構來接收處理 HTTP 請求。
疑問點:為什么調用 servlet.init()
?
- Servlet 規范要求,所有 Servlet 必須實現 javax.servlet.Servlet 接口;Java Servlet 規范(JSR 369)明確定義:
-
“After the servlet object is instantiated, the container must initialize the servlet before it can handle requests. The container initializes the servlet by calling the
init(ServletConfig)
method.” - load-on-startup 控制:在 web.xml 中配置的
<load-on-startup>
決定初始化時機;- 延遲加載(默認行為):在 Tomcat 容器啟動時,默認情況下 不會立即初始化
Servlet
。Servlet
的初始化通常是延遲的(lazy loading),即在第一次接收到與該 Servlet 相關的請求時才會進行初始化。這種行為是由 Servlet 規范定義的,目的是為了節省資源。 - 啟動時加載(eager loading):如果在 web.xml 中為 Servlet 配置了
<load-on-startup>
元素,Tomcat 會在容器啟動時初始化該 Servlet。其值為一個整數,表示加載順序。0或正值,值越小,優先級越高。負值或未指定,首次請求時初始化。
- 延遲加載(默認行為):在 Tomcat 容器啟動時,默認情況下 不會立即初始化
3.2 Spring MVC上下文啟動源碼解析
源碼入口:DispatcherServlet.init()
- 配置web.xml:
由于繼承關系,實際初始化入口類為DispatcherServlet
的父類HttpServletBean
,源碼位置:org.springframework.web.servlet.HttpServletBean
1. ★MVC容器初始化入口:HttpServletBean
2. 創建 MVC 容器:FrameworkServlet
3. 核心邏輯:initWebApplicationContext()
4. 創建MVC子容器:createWebApplicationContext()
5. 配置并刷新容器:configureAndRefreshWebApplicationContext()
6. 初始化 MVC 組件:DispatcherServlet 的 onRefresh()
3.3 核心啟動流程
DispatcherServlet
初始化時調用init()
方法。- 創建 子應用上下文(專用于 Web 層的容器),自動將根上下文設置為父容器。
- 加載
contextConfigLocation
指定的 MVC 配置(如 Controller、視圖解析器等)。 - 刷新子上下文(
refresh()
方法),初始化所有 MVC 相關的 組件Bean。
通過此流程,Spring MVC 實現了 Web 層組件的精確控制,同時通過父子容器隔離了業務層與 Web 層的 Bean 管理。
4. 關鍵設計解析
1. 父子容器設計的優勢
- 關注點分離:業務層與Web層解耦
- 資源隔離:避免Controller污染業務層
- 靈活配置:不同容器可獨立配置
- 依賴可控:子容器可訪問父容器,反之不行
- 獨立刷新:Web層重啟不影響業務層
2. 設計意義與價值
- 生命周期管理:
- 容器完全控制 Servlet 的創建 → 初始化 → 服務 → 銷毀
- 保證資源有序初始化和釋放
- 依賴解耦:
- Servlet 無需知道容器實現細節
- 通過標準接口
ServletConfig
獲取配置
- 資源預加載:
load-on-startup
避免首次請求延遲- 特別適合 Spring MVC 這類重量級前端控制器
- 擴展性:
- Spring 通過重寫
init()
插入自定義初始化邏輯 - 實現父子容器、組件初始化等高級特性
- Spring 通過重寫
三、調試技巧
關鍵斷點位置:
FrameworkServlet.initWebApplicationContext()
AbstractApplicationContext.refresh()
DispatcherServlet.initStrategies()
RequestMappingHandlerMapping.afterPropertiesSet()
四、總結
Spring MVC在Web容器中的啟動是一個精密的協作過程:整個啟動過程由 Servlet
規范 驅動(監聽器、Servlet 生命周期),Spring 在此基礎上擴展上下文層次。
- Tomcat 通過
load-on-startup
機制觸發Servlet
初始化 ContextLoaderListener
創建根容器管理業務BeanDispatcherServlet
創建子容器管理Web組件。- 父子容器 通過
setParent()
建立層級關系。 - 父子容器
refresh()
方法觸發完整的Bean初始化流程
通過這種分層設計,Spring 實現了關注點分離(業務層 vs Web 層),同時確保依賴注入的正確性。 通過這套機制,Tomcat
等容器保證了 Spring MVC 這類框架能在正確的時間點初始化自己的核心組件,同時遵循 Java EE 標準規范。
End!
擴展
DispatcherServlet Diagram
Tomcat 中的完整調用棧
// Tomcat 啟動入口
Bootstrap.main()→ Catalina.load()→ StandardServer.start()→ StandardService.start()→ StandardEngine.start()→ StandardHost.start()→ StandardContext.start()→ StandardContext.startInternal()→ StandardContext.fireLifecycleEvent() // 觸發監聽器(初始化Spring Web根容器)→ StandardContext.loadOnStartup() // 關鍵:啟動時加載Servlet→ StandardWrapper.loadServlet()→ DispatcherServlet.init(ServletConfig) // Spring MVC子容器入口→ HttpServletBean.init()→ FrameworkServlet.initServletBean()→ initWebApplicationContext() // 初始化MVC容器
Servlet 3.0+ 無配置啟動(Java Config)
通過實現 WebApplicationInitializer
接口替代 web.xml
:
public class MyWebAppInitializer implements WebApplicationInitializer {@Overridepublic void onStartup(ServletContext servletContext) {// 1. 創建根容器AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();rootContext.register(RootConfig.class);servletContext.addListener(new ContextLoaderListener(rootContext));// 2. 創建 MVC 容器(子容器)AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext();mvcContext.register(WebConfig.class);// 3. 注冊 DispatcherServletDispatcherServlet servlet = new DispatcherServlet(mvcContext);ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher", servlet);registration.addMapping("/");registration.setLoadOnStartup(1);}
}