Spring - DispatcherServlet是如何工做的?

本文中咱們將會看到,SpringMVC裏包含的DispatcherServlet是怎樣對Web程序開發產生巨大的影響的。html

clipboard.png

SpringMVC的心臟 - DispatcherSerlvet

咱們做爲Web應用程序的開發者,最想從如下這些枯燥乏味的工做中抽身出來,只關注真正的業務邏輯實現。java

  • 把一個HTTP request交給它真正的處理方法
  • 解析HTTP request的header和body中的數據,並把它們轉換爲DTO(數據傳輸對象)
  • Model-View-Controller三方的交互
  • 再把業務邏輯返回的DTO轉換成HTTP response,等等等等

SpringMVC中的DispatcherServlet正正就是提供這些服務的。它是
這個核心組件接收全部傳輸到你應用的HTTP requestweb

而且你會看到,DispatcherServlet具備強悍的可擴展性,例如,它容許你爲許多任務引入已存在的或你自定義的插件。spring

實現HandlerMapping接口

  • 把一個HTTP request交給它真正的處理方法

實現HandlerAdapter接口

  • 不管是普通的serlvet處理方法,仍是特定的設計模式例如MVC工做流,又或者僅僅只是一個POJO裏面的普通方法,均可以處理HTTP request

實現ViewResolver接口

  • 根據名稱解析出views模板,並容許你使用XML/XSLT或者任意其餘不一樣的模板引擎技術

實現MultipartResolver接口

  • 針對文件上傳類型的multipart requests,DispatcherServlet默認使用Apache Commons file uploading implementation (Apache通用文件上傳實現)。固然你也能夠實現你本身的MultipartResolver

實現LocaleResolver接口

  • 實現本身的LocaleResolver,經過cookie/session/已接收的HTTP頭部和任意其它數據,來解析出用戶所期待的語言環境。

對HTTP Request的處理

首先,讓咱們從Controller層中的方法開始,直到響應回用戶瀏覽器爲止,追蹤一下普通的HTTP requests的處理過程。設計模式

DispatcherServlet擁有很是長的繼承層次;然而,從上至下一個一個地理解這些獨特的層次是很是值得的:request的處理過程很是得有趣,並且理解HTTP request是理解MVC體系結構的關鍵部分(包括在標準開發期間的本地請求和遠程請求)。瀏覽器

clipboard.png

GenericServlet

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

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

再下一層,HttpServletBean是在這長串的繼承關係中,第一個Spring相關的類。它從web.xml或WebApplicationInitializer中加載servlet初始化參數,並注入到bean的properties中。架構

這個類僅負責初始化工做,它並無覆蓋HttpServlet的service()方法,由於它繼承了HttpServlet中對HTTP requests的處理邏輯,即僅分發doGet/doPost/doHead等等...

FrameworkServlet

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作到。

DispatcherServlet: 統一處理請求

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: 使Request更健壯

最終,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()對請求進行分發。

DispatcherServlet:分發請求

doDispatch()方法的主要目標:是爲request找到適配的handler(處理器),並將request/response 參數放入該處理器。這個處理器基本上能夠是任意的對象,Spring並無抽象出接口來規範這個處理器的實現方式,這同時就意味着,Spring須要找到一個adapter(適配器)來與這個handler進行通訊/對話/銜接。

而爲了找到適配的handler,Spring須要遍歷容器內已註冊的HandlerMapping實現類。(有很是多的不一樣的實現方式可供使用)

SimpleUrlHandlerMapping

能夠把URL映射到專門的Controller bean。

/welcome.html=ticketController
/show.html=ticketController

RequestMappingHandlerMapping

不過最普遍使用的固然是RequestMappingHandlerMapping,它能夠將請求映射至具體的@Controller類中的@RequestMapping方法

doDispatch()方法同時進行了其它的針對HTTP的特別任務:

  • 在資源未被修改的狀況下,對GET請求的短路處理
  • 對相應的request進行識別並自動使用MultipartResovler
  • 若是處理程序選擇異步處理請求,則縮短處理過程

HandlerAdapter對Request的處理

如今Spring已經決定好了將request分發給哪一個handler,而且已經找到了handler對應的adapter。那就是時候最終處理這個request了。
如下是HandlerAdapter.handle()方法的簽名,請注意到handler是能夠選擇如何處理這個request的。

  • 能夠直接將響應結果寫進response對象,並return null;
  • 也能夠返回一個ModelAndView對象給DispatcherServlet進行渲染;
@Nullable
ModelAndView handle(HttpServletRequest request, 
                    HttpServletResponse response, 
                    Object handler) throws Exception;

一樣,爲了多種場景,Spring也提供了多種類型的handler。

SimpleControllerHandlerAdapter

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對象,並無本身進行渲染。

SimpleServletHandlerAdapter

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

RequestMappingHandlerAdapter

針對最普遍使用的@RequestMapping註解,能夠本身查閱下實現代碼。

HandlerMethod對request參數和返回結果的處理

若是你已經有開發經驗,你會注意到@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

ViewResolver對View的渲染

好了,Spring經過handler終於處理完request,而且接收到了handler返回來的ModelAndView對象,它必須開始渲染用戶在瀏覽器裏會看到的HTML網頁了。
渲染操做的前提是ModelAndView中封裝的Model對象和View對象。 (= =這句翻譯怎麼那麼白癡...)

JSON/XML及任意其餘的數據格式,只要能經過HTTP協議進行傳輸,就可以被渲染。咱們會在下文中的 REST風格中再談。

仍是回到DispathcerServletrender()方法會這樣作:

  1. 使用LocaleResolver解析出用戶的語言環境,默認會使用AcceptHeaderLocaleResovler。(假設用戶設置了Request Headers: ACCEPT)。
  2. 選擇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終於完成了它的使命。讓咱們爲它鼓鼓掌,啪啪啪。

相關文章
相關標籤/搜索