本文中咱們將會看到,SpringMVC裏包含的DispatcherServlet是怎樣對Web程序開發產生巨大的影響的。html
咱們做爲Web應用程序的開發者,最想從如下這些枯燥乏味的工做中抽身出來,只關注真正的業務邏輯實現。java
HTTP request
交給它真正的處理方法HTTP request
的header和body中的數據,並把它們轉換爲DTO(數據傳輸對象)Model-View-Controller
三方的交互HTTP response
,等等等等SpringMVC中的DispatcherServlet
正正就是提供這些服務的。它是
這個核心組件接收全部傳輸到你應用的HTTP request
。web
而且你會看到,DispatcherServlet具備強悍的可擴展性,例如,它容許你爲許多任務引入已存在的或你自定義的插件。spring
HandlerMapping
接口HTTP request
交給它真正的處理方法HandlerAdapter
接口HTTP request
ViewResolver
接口MultipartResolver
接口multipart requests
,DispatcherServlet默認使用Apache Commons file uploading implementation (Apache通用文件上傳實現)
。固然你也能夠實現你本身的MultipartResolver
。LocaleResolver
接口LocaleResolver
,經過cookie/session/已接收的HTTP頭部和任意其它數據,來解析出用戶所期待的語言環境。首先,讓咱們從Controller層中的方法開始,直到響應回用戶瀏覽器爲止,追蹤一下普通的HTTP requests
的處理過程。設計模式
DispatcherServlet
擁有很是長的繼承層次;然而,從上至下一個一個地理解這些獨特的層次是很是值得的:request的處理過程很是得有趣,並且理解HTTP request
是理解MVC體系結構的關鍵部分(包括在標準開發期間的本地請求和遠程請求)。瀏覽器
GenericServlet是Servlet規範中的一部分,但它並不直接對接HTTP。它定義的service()方法,只聚焦於接收進入的requests和產生responses。
請注意:參數(沒有HTTP) ServletRequest
和(沒有HTTP) ServletResponse
:服務器
public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
這個service()是request在一個服務器中最終會經歷的方法,即使是最簡單的GET request
也會走到這裏。cookie
HttpServlet也是Servlet規範中的一部分,顧名思義,它僅聚焦於HTTP。
更細節的是,HttpServlet是一個abstract
類,它所實現的service()方法,僅僅是將HTTP requests
進行分類:session
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { // ... doGet(req, resp); } else if (method.equals(METHOD_HEAD)) { // ... doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); // ... } }
再下一層,HttpServletBean是在這長串的繼承關係中,第一個Spring相關的類。它從web.xml或WebApplicationInitializer
中加載servlet初始化參數
,並注入到bean的properties中。架構
這個類僅負責初始化工做,它並無覆蓋HttpServlet
的service()方法,由於它繼承了HttpServlet
中對HTTP requests
的處理邏輯,即僅分發doGet/doPost/doHead
等等...
FrameworkServlet將Servlet功能
與web application context
集成在一塊兒,實現了ApplictaionContextAware
接口,不過它也能夠本身親手建立出一個web application context
。
前文說過,它的父類 - HttpServletBean
,把servlet初始化參數注入到bean的屬性中。因此,若是serlvet參數中的contextClass
擁有參數值,那這個contextClass
的實例將會被建立,並被認爲是一個application context
。不然會使用默認的XmlWebApplicationConetext
.
隨着XML配置已經逐漸再也不流行,SpringBoot就將上述參數默認配置爲AnnotationConfigWebApplicationContext
。
你也能夠輕鬆地改變這個配置,例如,若是想要使用Groovy-based application context
的話,只需在web.xml
中配置:
dispatcherServlet org.springframework.web.servlet.DispatcherServlet contextClass org.springframework.web.context.support.GroovyWebApplicationContext
一樣的效果也能夠經過Java-based
方式的WebApplicationInitializer
作到。
HttpServlet.service()
實現,能夠經過區分請求行爲GET/POST
等,對request進行分發。這在初級的serlvets開發中是很是奏效的。然而,在SpringMVC的抽象模型中,由很是多的參數變量值來決定分發方式,而GET/POST/PUT
等,只是這衆多變量中的一小部分。
因而,FrameworkServlet
的其它主要方法,就是把衆多處理邏輯再歸類到一個統一的processRequest()
方法,在processRequest()
中最終會調用doService()
方法:
@Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } // …
最終,DispatcherServlet中的doService()
,會向request增長多個有用的屬性,例如web application context, locale resolver, theme resolver, theme source
等多個在以後的處理流程中能夠拿來即用的元素:
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
另外,doService()
方法也提供了對輸入和輸出的Flash Map
。Flash map基本上是一種將參數從一個請求傳遞到緊接着的另外一個請求的模式。這在重定向期間可能很是有用(例如在重定向以後向用戶顯示一次性信息消息):
FlashMap inputFlashMap = this.flashMapManager .retrieveAndUpdate(request, response); if (inputFlashMap != null) { request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); } request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
以後,doService()
方法調用doDispatch()
對請求進行分發。
doDispatch()
方法的主要目標:是爲request找到適配的handler(處理器)
,並將request/response 參數
放入該處理器。這個處理器基本上能夠是任意的對象,Spring並無抽象出接口來規範這個處理器的實現方式,這同時就意味着,Spring須要找到一個adapter(適配器)
來與這個handler
進行通訊/對話/銜接。
而爲了找到適配的handler,Spring須要遍歷容器內已註冊的HandlerMapping實現類
。(有很是多的不一樣的實現方式可供使用)
能夠把URL映射到專門的Controller bean。
/welcome.html=ticketController /show.html=ticketController
不過最普遍使用的固然是RequestMappingHandlerMapping
,它能夠將請求映射至具體的@Controller類中的@RequestMapping方法
。
doDispatch()
方法同時進行了其它的針對HTTP
的特別任務:
MultipartResovler
如今Spring已經決定好了將request分發給哪一個handler,而且已經找到了handler對應的adapter。那就是時候最終處理這個request了。
如下是HandlerAdapter.handle()
方法的簽名,請注意到handler是能夠選擇如何處理這個request的。
ModelAndView
對象給DispatcherServlet進行渲染;@Nullable ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
一樣,爲了多種場景,Spring也提供了多種類型的handler。
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((Controller) handler).handleRequest(request, response); }
這是SimpleControllerHandlerAdapater
處理一個SpringMVC的Controller實例
的方式。(請注意與@Controller註解的handler類
區分開來,它們是不一樣的。)
能夠看到,這個Controller實例
handler僅僅返回了ModelAndView
對象,並無本身進行渲染。
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((Servlet) handler).service(request, response); return null; }
SimpleServletHandlerAdapter
,是常規的servlet的適配器。
而一個常規的servlet
是並不知道MVC架構
的,因此它會直接處理這個request而且本身response進行渲染。能夠看到,handle()
方法直接return null
,而不是return ModelAndView
。
針對最普遍使用的@RequestMapping
註解,能夠本身查閱下實現代碼。
若是你已經有開發經驗,你會注意到@RequestMapping註解的handlerMethod
通常狀況下並不直接獲取HttpServletRequest/HttpServletResponse
參數,而是獲取/返回一些自定義的數據對象,例如DomainObject
/Entity
/DTO
/PathParameters
等等。
你也發現你不須要返回ModelAndView對象
。你可能只返回字符串
/ResponseEntity
/將被轉換爲JSON的POJO
等等。
RequestMappingHandlerAdapter
保證了handlerMethod接收的參數是確實地從HttpServletRequest
中解析出來的。同時,它也保證了handlerMethod的返回值會被轉換爲ModelAndView
。
如下這塊重要的代碼,就是RequestMappingHandlerAdapter
保證這些黑魔法必定會被執行的契約:
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers( this.argumentResolvers); } if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers( this.returnValueHandlers); }
this.argumentResolvers
對象由多個不一樣的HandlerMethodArgumentResolver實例
組成。
參數解析器的實現超過30種。這保證了request種任意種類的信息都可以順利轉換成方法的參數。例如URL path variables
/request body parameters
/request headers
/cookies
/session
等等數據。
this.returnValueHandlers
也是同理。
舉例來講,
hello()
方法返回一個字符串
時,相應的解析器就是ViewNameMethodReturnValueHandler
就會開始運做。ModelAndView
時,相應運行的則是ModelAndViewMethodReturnValueHandler
。好了,Spring經過handler終於處理完request,而且接收到了handler返回來的ModelAndView
對象,它必須開始渲染用戶在瀏覽器裏會看到的HTML網頁了。
渲染操做的前提是ModelAndView
中封裝的Model
對象和View
對象。 (= =這句翻譯怎麼那麼白癡...)
JSON/XML及任意其餘的數據格式,只要能經過HTTP協議進行傳輸,就可以被渲染。咱們會在下文中的
REST風格
中再談。
仍是回到DispathcerServlet
,render()
方法會這樣作:
LocaleResolver
解析出用戶的語言環境,默認會使用AcceptHeaderLocaleResovler
。(假設用戶設置了Request Headers: ACCEPT
)。選擇View
:
ModelAndView
,則能夠直接開始渲染了。若是僅僅返回了表明view名字的字符串
,則會經過ViewResolver
進行解析:
for (ViewResolver viewResolver : this.viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { return view; } }
this.viewResolvers
一樣包含了多種特型實現類,例如ThymeleafViewResolver -> Thymeleaf
。這些特型解析器知道專屬於本身的位置在哪裏,它們會自行去到對應的位置找到本身想要的View。
在render()
方法結束後,將HTML網頁
發送到用戶的瀏覽器,DispatcherServlet
終於完成了它的使命。讓咱們爲它鼓鼓掌,啪啪啪。