Spring MVC 的工作流程講解遍地開花,本篇主要記錄 DispatherServlet 的處理機制。這是深入瞭解 Spring MVC 框架工作機理的關鍵。
Spring MVC 的心臟
DispatcherServlet 是 Spring MVC 的心臟,它負責接收 HTTP 請求並協調 Spring MVC 的各個元件完成請求處理的工作。DispatcherServlet 本質也是一個 Servlet,使用者必須在 web.xml 中配置。
DispatcherServlet 處理機制
DispatcherServlet 細分之後,可以整理出三個功能:
- 截獲 HTTP 請求,並交由 Spring MVC 框架處理
- 處理控制層、業務層、持久層之間的呼叫關係
- 初始化並裝配 Spring MVC 的各個元件
配置 DispatcherServlet
在 web.xml 中配置一個 Servlet,並通過 < servlet-mapping> 指定其處理的 URL。
<!-- 1. 載入所有的配置檔案 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/**/applicationContext-*.xml</param-value>
</context-param>
<!-- 1. 配置Spring監聽 -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<!-- 2. 宣告 DispatcherServlet-->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 3. DispatcherServlet 匹配的 URL 模式-->
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>複製程式碼
在 1. 處,通過 contextConfigLocation 引數制定業務層和持久層的 Spring 容器的配置檔案(此處載入了全部配置檔案),可以載入單個,也可以載入多個(以“,”分隔)。ContextLoaderListener 是一個 ServletContextListener,它通過 contextConfigLoaction 引數所指定的 Spring 配置檔案啟動業務層和持久層的 Spring 容器。
在 2. 處配置了名為 springMVC 的 DispatcherServlet,它預設自動載入 /WEB-INF/springMVC-servlet.xml(即 < servlet-name>-servlet.xml) 的 Spring 配置檔案,啟動“控制層”的 Spring 容器。
當然,也可以顯示的指定 Spring 配置檔案,如此一來,配置檔案的名稱可不必遵守 < servlet-name>-servlet.xml 的命名規則:
<!-- 2. 宣告 DispatcherServlet-->
<servlet>
<servlet-name>springMVC</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- 顯示指定 “控制層” 的 Spring 配置檔案-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/config/springmvc/applicationContext-mvc.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>複製程式碼
一個 web.xml 可以配置多個 DispatcherServlet,要保證 < servlet-name> 不同,通過其 < servlet-mapping> 的配置,讓每個 DispatcherServlet 處理不同的請求。
截獲 HTTP 請求
在 3. 處通過 < servlet-mapping> 指定 DispatcherServlet 處理所有的 HTTP 請求,這裡的“/”表示瀏覽器中的 URL 都會被 DispatcherServlet 接收。大多數情況,我們都會讓 DispatcherServlet 處理以某種特定的 URL,比如以 .html 為字尾的 HTTP 請求:
<servlet-mapping>
<servlet-name>springMVC</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>複製程式碼
層級之間的呼叫
多個 Spring 容器之間可以設定(通過 IOC)為父子級的關係,實現良好的解耦。在這裡,“控制層” Spring 容器將作為“業務層” Spring 容器的子容器,即“控制層”容器可以引用“業務層”容器的 Bean,而“業務層”容器的卻訪問不到“控制層”容器的 Bean,“持久層”亦同。
裝配 Spring MVC 元件
在此之前,關於 DispatcherServlet 的瞭解都只停留在配置檔案的層面,配置檔案中只涉及了載入Spring配置檔案,以及截獲 HTTP 請求。如果要想知道其他關於 DispatcherServlet 的知識——初始化並裝配 Spring MVC 元件,就必須檢視原始碼了。正所謂:不入虎穴,焉得虎子?
IDE 提前安裝【原始碼檢視】外掛,以下是 org.springframework.web.servlet.DispatcherServlet 的 initStrategies() 的程式碼(註釋是我新增的):
protected void initStrategies(ApplicationContext context)
{
initMultipartResolver(context); // 初始化上傳檔案解析器
initLocaleResolver(context); // 初始化本地化解析器
initThemeResolver(context); // 初始化主題解析器
initHandlerMappings(context); // 初始化處理器對映器
initHandlerAdapters(context); // 初始化處理器介面卡
initHandlerExceptionResolvers(context); // 初始化處理器異常解析器
initRequestToViewNameTranslator(context); // 初始化請求到檢視名翻譯器
initViewResolvers(context); // 初始化檢視解析器
initFlashMapManager(context); // 初始化 Flash 管理器
}複製程式碼
一開始其實我不知道該定位到哪個方法或者說哪部分程式碼,但是我看到關鍵字 init,以及引數 ApplicationContext context 大概知道這應該是初始化方法了,而且該方法內呼叫了其他以 init 開頭的方法,而且方法名都是平常熟悉的詞彙,這些就是元件了,八九不離十。最後我通過直譯和找資料給上述程式碼新增了註釋。
initStrategies() 方法將在 WebApplicationContext 初始化後自動執行,此時 Spring 上下文中的 Bean 已經初始化完畢。該方法的工作是通過反射機制查詢並裝配 Spring 容器中使用者顯示自定義的元件 Bean,如果找不到再裝配預設的元件例項。
小結
要想深入 Spring MVC 框架的工作機理,必須先了解以下三個問題:
- DispatcherServlet 如何截獲 HTTP 請求
- 控制層、業務層和持久層之間的呼叫關係
- 如何初始化 Spring MVC 的各個元件,並將其裝配到 DispatcherServlet 中