天天看點

SpringMVC流程分析:揭開DispatcherServlet的神秘面紗

作者:愛做夢的程式員

前言

在使用SpringMVC開發Web應用時,除了會在web.xml中配置之前讨論的ContextLoaderListener項以外(相關内容可參考:SpringMVC流程分析(一):從一行配置入手,搞懂web環境下Ioc容器的建構),還需要在配置一個Servlet,而這個Servlet正是本文所讨論的重點——DispatcherServlet.

下圖展示了本系列文章重點分析的元件資訊,其中 DispatcherServlet是本文分析的重點。

SpringMVC流程分析:揭開DispatcherServlet的神秘面紗

Servlet的前世今生

随着Web開發工具的不斷疊代, Servlet可能已經成為一個相對陌生的話題。接下來,将以餐廳點菜為例,帶你快速熟悉Servlet.

當你走進一家餐廳,拿起菜單準備點菜時,總會有一個“服務員”會在你身旁,負責接收你的點菜資訊,然後将你的餐單交給後廚去處理,等後廚烹調完畢後,“服務員”會将菜肴端到你的桌前。

類似的,當你在浏覽器上通路一個網頁或送出表單時,Servlet就像是一個廚師和服務員的組合。它接收你的訂單(請求),然後根據你的選擇(資料),然後告訴廚師要做什麼(執行業務邏輯),最後将做好的食物端到你的桌子上(傳回動态生成的頁面),讓你享受美食.

簡而言之,Servlet的作用在于接收并處理用戶端的HTTP請求,并生成響應結果.

Servlet的體系結構

SpringMVC流程分析:揭開DispatcherServlet的神秘面紗
  • Servlet:Servlet體系根接口
  • GenericServlet:Servlet抽象實作類
  • HttpServlet: 對HTTP協定封裝的Servlet實作類

Servlet作為一個接口,并不能通過new關鍵字來進行執行個體化,是以如果我們期待享受Servlet所提供的服務,則必須依賴于Servlet的實作類HttpServlet,其擴充了GenericServlet類,專門用于處理HTTP請求.

此外,HttpServlet提供了對HTTP請求的封裝和處理方法,使得Servlet可以根據Http的請求方法的不同資訊,轉發至對應的處理方法,如Get請求轉發至doGet()處理,Post請求轉發至doPost()處理.

Servlet的生命周期

事實上,在Servlet對象的建立、初始化、接收請求、銷毀等過程中,都會執行對應的方法,其分别為init()、service()、destory()方法. 而這些方法也被稱為是Servlet的生命周期方法中. 這些方法的具體功能如下:

  • init():在Servlet對象被建立後,容器會調用init()方法進行初始化。該方法隻會在Servlet的整個生命周期中被調用一次。可以在init()方法中進行一些初始化操作,例如加載配置檔案、建立資料庫連接配接等。
  • service():每當有HTTP請求到達Servlet時,容器會調用service()方法來處理請求,并據請求的類型(GET、POST等)來調用相應的doGet()、doPost()等方法。在這些方法中,可以定義請求處理邏輯、業務操作邏輯等,并生成相應的HTTP響應.
  • destroy():在Servlet容器決定銷毀Servlet對象時,會調用destroy()方法。這通常發生在Web應用程式關閉或Servlet容器關閉的時候。在destroy()方法中,你可以進行一些資源釋放和清理的操作,例如關閉資料庫連接配接、釋放資源等.

總的來看,當Servlet對象建立時,會調用init()進行初始化;在接收HTTP請求時,則會調用service()方法處理請求;而在銷毀時會調用destroy()進行資源清理.

Servlet中的請求處理

SpringMVC流程分析:揭開DispatcherServlet的神秘面紗
  • 首先,浏覽器和伺服器建立連接配接,生成請求資料包,将請求資料包發送給伺服器;
  • 接着,伺服器解析請求資料包,建立request和response對象,将請求資料存入request對象中;
  • 随後,伺服器調用Servlet的service()方法,會将request和response作為參數傳進來,并将處理結果寫入到response中;
  • 最後,浏覽器解析response中的響應内容,在頁面上生成響應内容.

揭秘DispatcherServlet

在Spring MVC中,DispatcherServlet作為一個前端控制器,其負責接收所有的HTTP請求,同時,将請求分發給相應的處理器(Controller)來處理,并最終傳回響應結果。

DispatcherServlet體系結構

SpringMVC流程分析:揭開DispatcherServlet的神秘面紗

通過上圖不難看出,DispatcherServlet的體系結構可以分為兩條脈絡,一條以Servlet體系結構為基礎,另一條則以Spring為基礎,其中HttpServletBean則是兩者的一個媒介,進而使得Spring中的某個Bean具有Servlet的相關功能.

但我們并不打算詳細分析DispatcherServlet的體系結構,因為這樣容易讓我們陷入到具體細節中,不利于全局的視野建立. 是以,我們将精力集中在DispatcherServlet之上.

在接下來的分析中,請忘記掉上述DispatcherServlet複雜的繼承關系結構,隻需要記住:DispatcherServlet是一個Servlet. 是以,我們關注的内容聚焦在Servlet的初始化以及Http請求的處理上.

DispatcherServlet初始化的秘密

因為DispatcherServlet是一個Servlet,是以在DispatcherServlet對象被建立後,Tomcat容器會調用其中的init()方法來完成Servlet的初始化工作,是以若要分析DispatcherServlet初始化的秘密,就應該研究其DispatcherServlet中init()的方法.

SpringMVC流程分析:揭開DispatcherServlet的神秘面紗

DispatcherServlet中的init()方法的邏輯如上圖所示,不難發現其中核心方法為initWebApplicationContext,即初始化一個web容器. 其相關代碼如下:

(ps:initWebApplicationContext() 的相關邏輯定義在DispatcherServlet的父類FrameWorkServlet之中.)

FrameWorkServlet# initWebApplicationContext()
java複制代碼
public abstract class FrameWorkServlet {

    // 成員變量 webApplicationContext 用于儲存web容器
    private WebApplicationContext webApplicationContext;

    // ....省略其他無關方法
    protected WebApplicationContext initWebApplicationContext() {
       WebApplicationContext rootContext =
             WebApplicationContextUtils.getWebApplicationContext(getServletContext());
       WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
           //擷取目前環境中的web容器資訊,如果Spring和SpringMVC整合,則不需要重新建立
           wac = this.webApplicationContext;
           if (wac instanceof ConfigurableWebApplicationContext) {
              ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
              if (!cwac.isActive()) {
                    if (cwac.getParent() == null) {
                   //設定父子容器将Spring容器設為SpringMVC的父容器
                   cwac.setParent(rootContext);
                }
                 // 容器重新整理,将SpringMVC配置檔案中的屬性加載到容器中
                 configureAndRefreshWebApplicationContext(cwac);
              }
           }
        }
        // ....省略其他無關代碼 
        if (wac == null) {
           // 如果目前上下文中沒有web容器,則重新建立一個
           // 主要針對單獨使用SpringMVC時的情況
           wac = createWebApplicationContext(rootContext);
        }

       if (!this.refreshEventReceived) {
          // 調用onRefresh方法,進而為容器中初始化一些元件資訊
          // DispathcerServlet的核心元件都通過此處完成初始化
          synchronized (this.onRefreshMonitor) {
             onRefresh(wac);
          }
       }
       return wac;
    }
  
  // ....省略其他無關方法
}
           

通過FrameworkServlet的繼承體系我們可以看到,其實作了ApplicationContextAware接口,這使得其具有擷取到Spring中應用上下文(ApplicationContextContext)的能力. 而FrameworkServlet的成員變量webApplicationContext 可以将 Servlet 上下文與 Spring 容器上下文進行關聯,其實際類型 ConfigurableWebApplicationContext.

事實上,如果在web.xml 中配置的 ContextLoaderListener 監聽器初始化的容器上下文容器資訊. 那麼Spring 和 SpringMVC的容器之間便會存在一種父子關系,即 Spring 的容器是父容器,SpringMVC的容器為子容器.

通過上述分析不難看出DispatcherServlet在初始化階段做的最核心工作就是建構容器,然後加載DispatcherServlet的相關配置資訊.

DispatcherServlet請求處理的秘密

在Servlet的請求進行中,每當有Http請求到達Servlet時,都會調用其内部的service()方法來進行處理,并傳回相應的處理結果. 接下來,我們将分析DispatcherServlet的service()相關内容,進而分析清楚DispatcherServlet内部是如何來完成一個請求處理的.

在開始分析之前,我們應該明白當Http到達DispatcherServlet時,其首先會調用DispatcherServlet中的service()方法,但該方法的具體實作是在FrameworkServlet中,而FrameworkServlet的service方法則會通過super.service()繼續向上調用父類HttpServletBean中service()的方法,但HttpServletBean中并未重寫service()方法,是以隻能繼續向上找到其父類HttpServlet中的service()方法,而此時HttpServlet中的service()方法,邏輯如下:

HttpServlet #service()
java複制代碼protected void service(HttpServletRequest req, HttpServletResponse resp)
    throws ServletException, IOException {
    // 擷取請求方法
    String method = req.getMethod();
    // 當請求方式為Get時,調用doGet處理請求
    if (method.equals(METHOD_GET)) {
        doGet(req, resp);
    }  else if (method.equals(METHOD_POST)) {
        doPost(req, resp);

    }
    // ....省略其他無關代碼
}
           

不難發現HttpServlet中service()的主要邏輯為:首先,擷取到請求方法的類型,然後,調用doXXX()方法(例如doGet、doPost等)來處理請求,而這些方法的處理邏輯在FrameworkServlet中進行了重新定義,具體如下:

java複制代碼protected final void doGet(HttpServletRequest request, 
                            HttpServletResponse response)
      throws ServletException, IOException {
   // 交給DispatcherServlet進行實作
   processRequest(request, response);
}


protected final void processRequest(HttpServletRequest request, 
                                    HttpServletResponse response)
      throws ServletException, IOException {
    // ....省略其他無關代碼
   
   // doService邏輯交給子類DispatcherServlet實作
   doService(request, response);
   
   // ....省略其他無關代碼
}

           

是以,最終的所有處理邏輯都DispatcherServlet中的doService進行處理. 此時,請求處理的整個調用過程如下圖所示. 是以如果要分析Http請求在DispatcherServlet的處理過程,隻需要關注方法doService即可.

Service()的具體調用關系如下所示.

SpringMVC流程分析:揭開DispatcherServlet的神秘面紗

通過上圖可以知道,當一個Http請求到達DispatcherServlet後,其會通過内部的service()方法來完成請求的處理,并最終委托于DispatcherServlet的doService方法來進行處理.

(Ps:由于DispatcherServlet複雜的繼承關系,是以service()的調用過程稍顯複雜; 但通過梳理service()的調用鍊我們可以知道,所有Http請求的處理都會委托于DispatcherServlet中的doService.)

在doService中,所有的處理邏輯又會委托于 doDispatch進行處理,其核心代碼如下:

java複制代碼protected void doDispatch(HttpServletRequest request, 
                HttpServletResponse response) throws Exception {

        // 省略無關代碼....
   
       // 檢測請求是否為上傳請求,如果是則通過 multipartResolver 将其封裝成 MultipartHttpServletRequest對象
       processedRequest = checkMultipart(request);
 
       // 獲得請求對應的 HandlerExecutionChain 
       mappedHandler = getHandler(processedRequest);
         
       // 如果擷取不到,則根據配置抛出異常或傳回 404 錯誤
       if (mappedHandler == null) {
           noHandlerFound(processedRequest, response);
           return;
       }

       //  獲得目前 handler 對應的 HandlerAdapter 對象
       HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

       // 攔截器前置處理邏輯 
       if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
       }

       // 調用 handler 方法,也就是執行對應的方法,并傳回視圖
       mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
       
       // 後置處理 攔截器
       mappedHandler.applyPostHandle(processedRequest, response, mv);
     
      // 處理正常和異常的請求調用結果
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   
     // 省略其他無關代碼....
}
           

DispatcherServlet中的doDispatch方法是Spring MVC的核心方法,用于處理HTTP請求并進行請求的分發。它是根據請求的URL和請求類型(GET、POST等)來确定要調用哪個控制器(Controller)的方法,并處理控制器方法的傳回結果,最終生成HTTP響應,其大緻處理邏輯如下圖所示:

SpringMVC流程分析:揭開DispatcherServlet的神秘面紗
  1. 解析請求資訊: 首先,doDispatch方法會解析HTTP請求的URL、請求類型、請求參數等資訊,以确定要調用哪個控制器的哪個方法來處理請求。
  2. 查找處理器(Handler): 接下來,doDispatch會根據請求的URL查找合适的處理器(通常是Controller對象)和處理器方法。這個過程是通過Spring的HandlerMapping元件來實作的,HandlerMapping會根據URL和配置的映射規則,找到對應的Controller和方法。
  3. 執行處理器方法: 一旦找到處理器和方法,doDispatch會調用該方法,并将HTTP請求的參數傳遞給方法。Controller方法會執行業務邏輯,并傳回一個包含響應資料的ModelAndView對象。
  4. 處理傳回結果: 接着,doDispatch會根據Controller方法傳回的ModelAndView對象,來決定如何處理響應結果。如果傳回結果是一個視圖(View),doDispatch會通過ViewResolver将邏輯視圖名稱解析為真實的視圖對象,然後使用視圖對象來渲染生成HTML等内容。
  5. 發送響應: 最後,doDispatch将生成的響應内容發送給用戶端(通常是浏覽器),完成HTTP響應的過程。

DispatcherServlet中的doDispatch方法可以根據HTTP請求的資訊,找到合适的控制器并調用處理器方法,然後處理傳回結果,最終生成HTTP響應。是以一定程度上,可以将doDispatch認為是Spring MVC的核心,是以後續我們的讨論都将圍繞上圖doDispatch的處理邏輯展開.

總結

本文首先介紹了Servlet的相關内容,并以此為基礎分析了Spring MVC中的核心元件DispatcherServlet,在整個過程中,我們所秉持的理念一直都是忽視掉DispatcherServlet複雜的繼承關系,而将其看做是一個Servlet,進而從Servlet的角度,并對DispatcherServlet的初始化和請求處理進行了詳細的分析.

不過在整個處理過程中涉及到SpringMVC中處理請求的元件還沒有進行分析,或許你對于許多細節存在疑惑,但不要慌,後續文章将對 SpringMVC的其他核心元件進行分析. 而DispatcherServlet 中的doDispatch方法中的處理邏輯将貫徹整個分析過程,這樣有利于加深我們對 SpringMVC 的了解,同時能将DispatcherServlet 元件同其他元件串聯起來,進而更好的了解SpringMVC中對于一個請求的處理.

繼續閱讀