1. 程式人生 > >SpringMVC核心控制器DispatcherServlet類結構原始碼閱讀

SpringMVC核心控制器DispatcherServlet類結構原始碼閱讀

SpringMVC是近年來出現的一個非常優秀的web框架,它是基於MVC思想設計的,採用鬆散耦合可插播元件結構,比其他的MVC框架更加靈活,更具可擴充套件性,此外SpringMVC在檢視解析、資料繫結等方面等有著非常不錯的表現,而已成為當今最受歡迎的MVC框架。

1體系結構

SpringMVC的核心是DispatcherServlet,它主要用於接收請求後,協調各個元件完成對請求的響應。和其他J2EE的MVC框架一樣,SpringMVC也是利用前端Servlet接收請求,處理請求,最後返回相應結果的。而DispatcherServlet即是SpringMVC對應的前端Servlet。

圖1.1 SpringMVC體系結構

SpringMVC在接收到請求後,主要是按照以下的流程處理並響應請求的。

(1)客戶端發起Http請求,web應用伺服器(如Tomcat)接收到請求後,如果請求格式匹配web.xml中配置的DispatcherServlet攔截請求格式(如*.html),那麼web伺服器將會把該請求轉交給DispatcherServlet進行處理。

(2)DispatcherServlet接收到請求後,會根據請求的資訊(如URL,請求方法等)從HandlerMapping中找到處理對應請求的處理器(Handler)。

(3)DispatcherServlet會將HandlerMapping返回的Handler交給HandlerAdapter進行包裝。再統一由HandlerAdapter的介面卡介面實現類通過介面實現呼叫Handler(具體HandlerAdapter如何實現利用介面卡介面呼叫Handler後續會進行分析)。

(4)Handler處理完請求對應的業務邏輯後會返回ModleAndView給DispatcherServlet。其中ModleAndView包含Handler處理後返回的邏輯檢視名以及資料物件。

(5)DispatcherServlet利用ModleAndView中的邏輯檢視名通過ViewResolver解析到真正的檢視物件View。

(6)DispatcherServlet獲取到檢視物件View後,會整合ModleAndView中的資料物件Modle進行檢視渲染。

(7)渲染完成後返回客戶端響應資訊。

2類結構

圖1.2

既然DispatcherServlet歸根到底是SpringMVC用於接收請求的Servlet,那就必須講一下DispatcherServlet的類結構(如圖1.2所示)。DispatcherServlet類結構主要是包含用於接收請求的Servlet部分以及用於監聽容器事件的Listener部分。DispatcherServlet主要是通過繼承FrameworkServlet實現對使用者請求的處理以及為每一個請求的Servlet管理WebApplicationContext例項。

類結構的Servlet部分中,Servlet介面是Servlet型別的頂級介面,定義了所有子類都必須實現的方法,如Servlet生命週期中的核心方法init(ServletConfig config),service(ServletRequestreq, ServletResponse res)以及destroy()等,如圖1.3所示。

圖1.3

GenericServlet實現Servlet以及ServletConfig介面,定義了通用的,與協議無關的Servlet。GenericServlet方法定義如圖1.4所示。

圖1.4

GenericServlet 使編寫Servlet 變得更容易,它提供生命週期方法init() 和destroy()的簡單版本,以及ServletConfig()介面中的方法的簡單版本。如果需要需要編寫基於特定協議的Servlet,只需要拓展GenericServlet即可。從類結構中可以看到,HttpServlet是基於Http協議擴充套件了GenericServlet的抽象類。

ServletConfig介面主要是用於定義Servlet容器通過Servlet配置物件在Servlet例項初始化的時候傳遞配置資訊。ServletConfig方法定義如圖1.5所示:

圖1.5

HttpServlet是基於Http協議的Servlet擴充套件,繼承了GenericServlet。HttpServlet定義多個用於處理不同型別Http請求的方法。Http請求目前支援7中方法,分別為 GET 、 POST 、  PUT 、 DELETE 和以及TARCE、HEAD 、 OPTIONS 。其中前四個方法為請求資料相關的操作,後三個方法主要用於查詢、測試伺服器或者請求相關的資料,具體如圖1.6所示:


圖1.6

(1)GET---請求獲取伺服器由Request-URI所標識的資源;

(2)POST---在Request-URI所標識的資源後附加新的資料,可以理解為向伺服器提交資料;

(3)PUT---請求伺服器儲存一個資源,並用Request-URI標識資源,可以理解為向伺服器的某個目錄放置資源;

(4)DELETE---刪除由Request-URI所標識的資源;

(5)TARCE---請求伺服器回送收到的請求資訊,主要用語測試或診斷;

(6)HEAD---請求獲取由Request-URI所標識的資源的響應訊息報頭;

(7)OPTIONS---請求查詢伺服器的效能,或查詢與資源相關的選項和需求;

HttpServlet的核心方法service(HttpServletRequestreq, HttpServletResponse resp)用於處理請求,子類完全可以不從寫該方法,因為該方法主要用於獲取到請求方法型別後,呼叫對應型別的處理方法處理請求即可,具體如圖1.7所示:

圖1.7

HttpServletBean是對HttpServlet的簡單擴充套件,主要是將web.xml中<servlet></servlet>標籤中的<init-param></init-param>標籤配置的引數作為Servlet的屬性在Servlet初始化的時候注入到Servlet的例項物件中。如圖1.8所示:

圖1.8

其中的PropertyValues主要是將web.xml中配置的Servlet中的初始化引數轉換為物件,其格式實際上也是key-value的形式,具體轉換方式如圖1.9所示:


圖1.9

其中的requiredProperties是HttpServletBean的屬性,Set中儲存的屬性名稱是當前Servlet例項化必需的。通過ServletConfig物件獲取當前Servlet配置的初始化引數名稱以及值。通過遍歷所有的初始化屬性將放置到PropertyValues介面的實現類例項的propertyValueList屬性中,如圖1.10所示:

 

圖1.10

每次新增一個PropertyValue例項進入到propertyValueList之前,首先遍歷propertyValueList,如果該屬性在propertyValueList中已經存在,則合併屬性,將合併後的PropertyValue例項放置到已經存在的PropertyValue例項的位置,並且返回。反之則直接新增到propertyValueList中。

將PropertyValue新增propertyValueList後,將該PropertyValue名稱從requiredProperties移除,用於標識該屬性有初始化值。遍歷執行完成後,如果requiredProperties仍然不為空,則表示有初始化必須的屬性沒有進行配置,則丟擲異常,Servlet初始化失敗。

圖1.8中PropertyValues例項化後包含注入的所有必需的屬性,然後通過BeanWrapper例項封裝了一個當前Servlet例項的行為,設定以及獲取當前Servlet例項的屬性值。其中registerCustomEditor()方法主要是註冊使用者自定義的屬性編輯器,可以根據具體需要對Servlet的屬性進行設定。子類可以通過從寫initBeanWrapper()方法對BeanWrapper例項進行特定的例項化。

這樣做的主要目的是將Servlet初始化引數(init-param)設定到該元件上(如contextAttribute、contextClass、namespace、contextConfigLocation),通過BeanWrapper簡化設值過程,方便後續使用;

另外initServletBean()方法提供給子類初始化的擴充套件,該方法FrameworkServlet覆蓋。

FrameworkServlet是Spring框架中的web層Servlet的基類,提供整合的Spring容器ApplicationContext上下文。FrameworkServlet主要做兩件事情,如圖1.11所示:

       (1)初始化WebApplicationcontext,為每一個請求管理對應的上下文例項;

       (2)提供initFrameworkServlet()方法提供子類進行初始化擴充套件;


圖1.11

其中initWebApplicationContext()用於初始化當前Servlet例項的WebApplicationContext例項。首先通過WebApplicationContextUtils到ServletContext中去獲取根上線文,如圖1.1.2所示:


圖1.1.2

其中根上下文的在ServletContext中的屬性名稱是WebApplicationContext.class.getName()+ ".ROOT",那麼根上下文具體是什麼?又是在什麼時候被設定到ServletContext中呢?還記得web.xml中的Spring容器初始化的配置嗎?如圖1.13所示:


圖1.13

該段配置以後,Tomcat容器啟動後會載入當前的配置,生成Spring的上下文,這就是根上下文,主要包含通用的Spring容器中的bean(如Dao,Service等),但是不包含web層的相關bean(如controller等),為什麼要這樣做呢?個人感覺是通過這種方式例項化容器中的bean後可以輕鬆的與其他的框架進行整合,比如Spring MVC,Struts2等,因為這些web框架在例項化各自的web層控制器時有各自的機制和方式,但是下層的bean的例項化方式卻是一致的。

根上下文建立好後就會被放置到ServletContext中,即在ContextLoader中的initWebApplicationContext()方法中進行設定,如圖1.14所示:


圖1.14

那麼為什麼要把根上下文放置到ServletContext中呢?其實這是和ServletContext的定義以及SpringMVC框架中核心控制器DispatcherServlet處理請求的機制有關。

web容器在啟動時,它會為每個WEB應用程式都建立一個對應的ServletContext物件,它代表當前web應用。 ServletConfig物件中維護了ServletContext物件的引用,開發人員在編寫Servlet時,可以通過ServletConfig.getServletContext方法獲得ServletContext物件。由於一個WEB應用中的所有Servlet共享同一個ServletContext物件,因此Servlet物件之間可以通過ServletContext物件來實現通訊。而在web.xml中配置的DispatcherServlet的配置內容如圖1.15所示:


圖1.15

DispatcherServlet初始化的上下文載入的Bean是隻對Spring MVC有效的Bean,如Controller、HandlerMapping、HandlerAdapter等等,該初始化上下文應該只加載Web相關元件。

但是Spring MVCweb等的相關元件bean在例項化的時候需要訪問底層的其他層的bean例項(如service層的業務邏輯bean),這時候怎麼辦呢?這時候就需要DispatcherServlet中例項化的上下文繼承根上下文,這樣便可以直接訪問到其他層次的bean,同時也實現了底層bean與web層框架的解耦。

DispatcherServlet繼承FrameworkServlet,實現了onRefresh()方法,同時完成web層前端控制器相關資源的初始化工作。如圖1.16所示:


圖1.16

從initStrategies()方法我們可以看出DispatcherServlet例項化時會初始化web層相關的bean,如HandlerMapping,HandlerAdapter等,並且如果我們沒有進行配置,DispatcherServlet會提供預設的配置。

從以上的Servlet的體系結構以及DispatcherServlet的例項化過程我們可以看出主要完成以下幾個事情:

(1)通過配置Servlet實現SpringMVC核心控制器DispatcherServlet的初始化;

(2)通過ServletContext共享Spring根上下文,使得每一個Servlet例項獲取根上下文中的bean,用於例項化SpringMVC web層的相關bean。

(3)初始化DispatcherServlet作為核心控制器,接收處理請求需要的相關資源,如HandlerMapping,HandlerAdapter等。

(4)通過Servlet體系結構中的繼承關係以及抽象方法,可以根據具體的需求對各個層級的Servlet抽象方法進行重寫以滿足不同的功能需要,父類中只定義流程和方法引用,具體實現由子Servlet完成,實現定義與實現的分離,便於擴充套件。

下篇將介紹DispatcherServlet如何作為核心控制器實現請求的接收,處理請求。