理解SpringMvc架構以及流程

本文是基于慕課網小馬哥的 《Spring Boot 2.0深度實踐之核心技術篇》的內容結合自己的需要和理解做的筆記。

理解 Spring Web MVC 架構

在解釋Spring Web Mvc 的架構之前,首先我們要了解一下基于Servlet的基礎架構之上構建的一種J2EE的設計模式--Front Controller(前端總控制器模式)

前端總控制器.png

通過流程圖我們可以了解到:

  1. 當客戶端發(fā)送請求到前端總控制器(Front Controller),在這里前端總控制器有兩種實現《Servlet》和《JSP》穷遂。
  2. 前端總控制器通過委派發(fā)送給應用控制器(ApplicationController)栖茉。
  3. 應用控制器通過委派調用一個命令對象(Command)楼咳,然后分發(fā)給對應的視圖處理器(View)以清。

介紹完前端總控制器凸舵,接下來對于理解SpringMvc架構就很簡單了叠必。

mvc架構圖.png

通過流程圖我們可以了解到:

  1. 通過客戶端發(fā)送的請求到前端總控制器(Front Controller) 也就是我們經常提到的DispatcherServlet荚孵。
  2. 前端總控制器委派給Controller,也就是我們常用的SpringMVC使用的控制層纬朝,也就是標注@Controller的業(yè)務模型收叶。
  3. 然后通過DispatcherServlet將業(yè)務處理的結果委派給視圖渲染模型。
  4. 最終返回視圖對象給DispatcherServlet,響應結果玄组。

SpringMvc核心組件以及交互流程

SpringMvc核心組件

  • 處理器管理
    • 映射:HandlerMapping
    • 適配:HandlerAdapter
    • 執(zhí)行:HandlerExecutionChain
  • 渲染
    • 視圖解析:ViewResolver
    • 國際化: LocaleResolver,LocaleContextResolver
    • 個性化:ThemeResolver
  • 異常處理
    • HandlerExceptionResolver

各個組件的介紹內容在Web on Servlet Stack中的 1.2.2. Special Bean Types章節(jié)中滔驾。

SpringMvc交互流程

交互流程.png

流程說明

  1. 用戶發(fā)送請求至前端控制器 DispatcherServlet谒麦。
  2. DispatcherServlet 收到請求調用 HandlerMapping 處理器映射器。處理器映射器根據請求 url 找到具體的處理器哆致,生成處理器對象及處理器攔截器(如果有則生成)一并返回給 DispatcherServlet绕德。
  3. DispatcherServlet 通過 HandlerAdapter 處理器適配器調用處理器。
  4. HandlerAdapter 執(zhí)行Controller層中的對應方法摊阀。
  5. Controller 執(zhí)行完成返回 ModelAndView或ViewName耻蛇。 HandlerAdapter 將 handler 執(zhí)行結果 ModelAndView 返回給 DispatcherServlet。
  6. DispatcherServlet 將 ModelAndView 傳給 ViewReslover 視圖解析器胞此。
  7. ViewReslover 解析后返回具體 View 對象臣咖。 DispatcherServlet 對 View 進行渲染視圖(即將模型數據填充至視圖中)。
  8. DispatcherServlet 響應用戶漱牵。

源碼理解交互流程

準備一個簡單的SpringMVC項目

在Debugger源碼之前夺蛇,先讓我們創(chuàng)建一個簡單的spring項目,只需要一個簡單的請求返回一個JSP頁面就可以酣胀。

我創(chuàng)建的spring項目全部都使用了注解驅動(版本必須是Spring Framework 3.1 +,Servlet 3.0)刁赦。

在這里不闡述是如何實現自動裝配Spring MVC項目的,后面會有單獨的介紹,先簡單給出代碼讓我們的程序跑起來闻镶,方便一步一步Debugger甚脉。

項目目錄如下:

項目目錄.png

pom.xml

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.0.4.RELEASE</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>

這里需要注意一下 我使用的是IntelliJ IDEA 商業(yè)版,所以不需要內置tomcat插件铆农。

WEB-INF/jsp/index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<p>Hello World</p>

</body>
</html>

替換web.xml的配置類 --DefaultAnnotationConfigDispatcherServletInitializer

package com.web.servlet.support;

import com.web.configuration.DispatchServletConfiguration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * web.xml的配置
 */
public class DefaultAnnotationConfigDispatcherServletInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {    //配置DispatcherServlet
        return new Class[]{DispatchServletConfiguration.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

替換application-context.xml的配置類 --WebMvcConfig

package com.web.configuration;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.servlet.DispatcherServlet;

/**
 * {@link DispatcherServlet}
 *  掃描使用注解驅動的包  
 * 相當于 <context:component-scan base-package="com.web"/>
 */
@ComponentScan(basePackages = "com.web")  
public class DispatchServletConfiguration  {
}

以上的XML配置和java代碼通過對應的注釋已經一一對應解釋了牺氨。

簡單的Controller層 --HelloWorldController

package com.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * 簡單controller
 */
@Controller
public class HelloWorldController {
    @RequestMapping("/index")
    public String index() {
        return "index";
    }
}

這里全部代碼已經給出。下面讓我們開始調試吧墩剖。讓我們啟動tomcat,訪問http://localhost:8080/index猴凹。

執(zhí)行流程

流程的核心就是 org.springframework.web.servlet.DispatcherServlet這個類。

核心方法就是#doDispatch()方法涛碑。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // Determine handler for the current request.
         // 確定當前請求的處理器也就是各種HandlerMapping
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // Determine handler adapter for the current request.
         // 確定當前請求的適配器
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (logger.isDebugEnabled()) {
               logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
            }
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }

         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return;
         }

         // Actually invoke the handler.
         // 調用實際的方法
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         applyDefaultViewName(processedRequest, mv);
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}

根據上面的源碼我們可以看到核心的過程

     // Determine handler for the current request.
     // 確定當前請求的處理器也就是各種HandlerMapping
     mappedHandler = getHandler(processedRequest);
     
      // Determine handler adapter for the current request.
     // 確定當前請求的適配器
     HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
         
     // Actually invoke the handler.
     // 調用實際的方法
     mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

現在讓我們分步來debugger這幾步精堕,一點一點理解其流程。

1.org.springframework.web.servlet.DispatcherServlet#getHandler

gethandler.png

我們可以看到spring默認實現了 HandlerMapping的5個實現類蒲障,而第一個就是我們配置常用的 RequestMappingHandlerMapping

接著往下看,通過RequestMappingHandlerMapping 獲取當前請求的處理程序執(zhí)行鏈也就是 HandlerExecutionChain 包含當前處理程序對象和任何處理程序攔截器瘫证。

executionChain.png

我們可以清楚的看到,我們請求的URL路徑直接映射到了public java.lang.String com.web.controller.HelloWorldController.index() 這個方法揉阎。

2.org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter

gethandler.png

我們可以看到spring默認實現了 HandlerAdapter的3個實現類,而第一個就是我們配置常用的RequestMappingHandlerAdapter

然后往下Debugger

getHandlerAdapt2.png

這一步是判斷當前的HandlerMethod是否是期望實現的處理程序背捌。最后返回對應的適配處理器毙籽。

3.org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle

handle.png

使用給定的適配處理程序來處理此請求,返回ModelAndView對象,這里viewName是 “index”,我們可以對應剛剛我們寫的HelloWorldController#index()方法毡庆,確實方法返回的就是"index" 字符串坑赡。

4.org.springframework.web.servlet.DispatcherServlet#processDispatchResult

這個方法的目的就是 解析程序選擇處理或者處理程序調用的結果無論是ModelAndView或者Exception烙如。

render.png

最后通過render方法來渲染視圖,我們在進入render方法中看一下就可以查找到我們想要的org.springframework.web.servlet.view.JstlView 和對應返回的資源URL毅否。

view.png

至此亚铁,SpringMvc大致的執(zhí)行流程已經介紹完了。我們通過Debugger源碼再對應上面的交互流程圖螟加,就可以理解SpringMVC的架構和流程了徘溢。

DEMO地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市捆探,隨后出現的幾起案子然爆,更是在濱河造成了極大的恐慌,老刑警劉巖黍图,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件曾雕,死亡現場離奇詭異,居然都是意外死亡助被,警方通過查閱死者的電腦和手機剖张,發(fā)現死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恰起,“玉大人修械,你說我怎么就攤上這事〖炫危” “怎么了肯污?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吨枉。 經常有香客問我蹦渣,道長,這世上最難降的妖魔是什么貌亭? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任柬唯,我火速辦了婚禮,結果婚禮上圃庭,老公的妹妹穿的比我還像新娘锄奢。我一直安慰自己,他們只是感情好剧腻,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布拘央。 她就那樣靜靜地躺著,像睡著了一般书在。 火紅的嫁衣襯著肌膚如雪灰伟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天儒旬,我揣著相機與錄音栏账,去河邊找鬼帖族。 笑死,一個胖子當著我的面吹牛挡爵,可吹牛的內容都是我干的竖般。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼了讨,長吁一口氣:“原來是場噩夢啊……” “哼捻激!你這毒婦竟也來了?” 一聲冷哼從身側響起前计,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胞谭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后男杈,有當地人在樹林里發(fā)現了一具尸體丈屹,經...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年伶棒,在試婚紗的時候發(fā)現自己被綠了旺垒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡肤无,死狀恐怖先蒋,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情宛渐,我是刑警寧澤竞漾,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站窥翩,受9級特大地震影響业岁,放射性物質發(fā)生泄漏。R本人自食惡果不足惜寇蚊,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一笔时、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仗岸,春花似錦允耿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姚垃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盼忌,已是汗流浹背积糯。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工掂墓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人看成。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓君编,卻偏偏與公主長得像,于是被迫代替她去往敵國和親川慌。 傳聞我的和親對象是個殘疾皇子吃嘿,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內容