03.02 別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

前言

提到框架,就不得不提一下看源碼,我們平時總是想求大神帶我們飛,然而看源碼就是一個向大神學習的最直接的一種方式,然而我們每次鼓起勇氣看源碼前是這樣的


但是一點開源碼,頓時代碼如洪流湧入,你的內心可能是這樣的


所以我在之前別怕看源碼,一張圖搞定Mybatis的Mapper原理的時候也提到過,Mybatis的源碼相對其他框架而言比較簡單,比較適合剛開始克服恐懼心理看源碼實戰,由於Struts2前不久又傳出安全性問題,所以Java開發中,表現層框架基本都是SpringMVC,那麼我們就來撕、拉、扯下SpringMVC的神秘外衣,可以對比之前別怕,Struts2執行流程沒那麼難,本篇中會涉及到一些的Struts2、JavaWeb以及SpringMVC使用上你一些細節.

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

SpringMVC執行流程圖.png

這是一個最經典的SpringMVC執行流程圖,相信做Java開發的都看過,其中有三個核心的地方,分別是HandlerMapping、HandlerAdapter、HttpMessageConveter.看完這個圖有了大局觀之後,就要開車了,前方高能,請扶穩坐好.

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

看源碼,首先要找到入口,那麼入口在哪?從流程圖我們就可以看出, DispatcherServlet就是入口核心類(其實從SpringMVC的配置文件也可以得知),但是這裡面有這麼多方法,我們又知道哪個方法才是入口?我們先來看一下DispatcherServlet的繼承圖

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

繼承圖.png

從這裡就可以看出,DispatcherServlet的本質就是Servlet,那麼我們回憶一下Servlet的生命週期,生命週期中主要的三個方法是void init(ServletConfig config)、void service(ServletRequest req, ServletResponse res)、void destroy(),但是我們又發現DispatcherServlet裡面根本就沒有service這個方法,那麼這個時候就要找它的父類FrameworkServlet.於是我們在service方法中打上斷點,開始發起請求,如圖.super.service(request, response);這裡會根據得到的請求類型,調用對應的方法(doGet或者doPost),比如我們這次測試是get請求,所以調用的是doGet.

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

doGet.png

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

processRequest.png

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

doService.png

doDispatch,從字面理解就知道,這個方法是分發請求的,核心的邏輯都在這裡

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

doDispatch.png

checkMultipart這個方法是檢查是否是二進制的請求(文件上傳的請求)

<code>    protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
//multipartResolver這是個視圖解析器,所以這裡是判斷一下有沒有視圖解析器,以及request是不是一個二進制請求
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
"this typically results from an additional MultipartFilter in web.xml");
}
else if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) instanceof MultipartException) {
logger.debug("Multipart resolution failed for current request before - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
// 如果是二進制的話,把request包裝一層,返回MultipartHttpServletRequest
return this.multipartResolver.resolveMultipart(request);
}
}
// If not returned before: return original request.
return request;
}
/<code>

因為不是二進制請求,返回的還是原來的對象,所以multipartRequestParsed = (processedRequest != request);的結果是false

下面高潮來了mappedHandler = getHandler(processedRequest);

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

getHandler.png

從單詞HandlerExecutionChain就知道,這個是處理執行鏈

HandlerMapping

HandlerMapping 就是請求處理映射器,它能根據不同的請求,選擇最合適的handle(自己編寫的控制器),請求處理映射器可以配置多個,誰最先匹配執行誰,

那麼這個for...in它在遍歷些什麼東西呢?其實這個在DispatcherServlet文件中已經有配置了

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

handlerMapping.png

其實這個就是包裝了不同的Mapping來判斷是通過BeanNameUrl的方式還是Annotation的方式來配置,那什麼是BeanNameUrl的方式呢?就是我們平時在xml文件中配置的

<code><bean>
/<code>

通過這個,把request傳進入得到HandlerExecutionChain

<code>HandlerExecutionChain handler = hm.getHandler(request);
/<code>

HandlerExecutionChain

HandlerExecutionChain(處理執行鏈)包含兩部分內容,一部分是請求對應的控制器,一部分是攔截器,真正執行handle之前,有一系列操作,例如數據轉換,格式化,數據驗證這些,都是由攔截器來做的

另外需要注意的是,假如你自定義了n個攔截器,會發現HandlerExecutionChain會有n+1個攔截器,說明有一個是他內部有的,從這裡我們可以知道它的執行順序,比如這裡要先執行攔截器,再執行我們控制器,所以這個東西被稱為處理執行鏈

下面又來到了第二波高潮(這個時候那些嘴上說不要的同學,身體還是要很誠實的繼續往下看),HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

getHandlerAdapter.png

HandlerAdapter

HandlerAdapter(處理適配器)這個翻譯成中文可能比較low,但是從名稱我們就可以得知,這個是用來執行handler(控制器),那這個for...in究竟在遍歷些什麼呢?其實這個在配置文件中也是有配置好的了

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

HandlerAdapter.png

這裡是判斷handle適不適合這個RequestMappingHandleAdapter,適合就返回

<code>if (ha.supports(handler)) {
return ha;
}
/<code>

接著往下走

<code>String method = request.getMethod();
/<code>

這個方法是獲取方法類型的,那麼這個get和post請求有什麼區別呢?get請求是有一個緩存的,但是post請求是不會有的

接著往下走

<code>if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
/<code>
別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

applyPreHandle.png

在這裡我們可以回憶一下HandlerInterceptor的三個方法

<code>public class MyInterceptor implements HandlerInterceptor {

//表示控制器方法執行之前調用的方法,返回結果為boolean,如果為true,表示放行,如果為false,表示攔截
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println("MyInterceptor.preHandle");
return true;
}

//控制器執行完方法之後,視圖結合之前
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor.postHandle");
}

//視圖結合完成之後調用的方法
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("MyInterceptor.afterCompletion");
}
}
/<code>

這裡主要是遍歷攔截器,如果返回的是false,從!mappedHandler.applyPreHandle(processedRequest, response這個判斷可以得知,就不再繼續往下執行了.

繼續往下走

<code>// Actually invoke the handler.
mv = ha.handle(processedRequest, response,mappedHandler.getHandler());
/<code>

從註釋上看,這裡去調用handle的方法,這個方法會做很多事情,比如之前提到的參數自動注入就是在這個步驟做的,這個步驟層級結構太深,篇幅有限,暫時不探討,這個時候把斷點打到控制器的方法上

<code>@RequestMapping("/test")
public String test(Model model) {
model.addAttribute("msg", "Hello Toby");
return "hello";
}
/<code>

再繼續往下走

<code>//默認視圖名稱
applyDefaultViewName(request, mv);
/<code>

這個默認的視圖名稱又什麼用呢?我們在使用上是不是遇到過直接返回Model但是沒有View的情況,例如

<code>@RequestMapping("/value2")
public User value2() {
//報錯:Circular view path [value2]: would dispatch back to the current handler URL [/value2] again
//此時該方法只有模型,沒有視圖,SpringMVC會默認給你視圖,默認的視圖名為:請求的名字(/value2)
//相當於又去重新請求/value2
return new User("toby","24");
}
/<code>

繼續往下走mappedHandler.applyPostHandle(processedRequest, response, mv);

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

applyPostHandle.png

從這裡我們就知道的執行順序是反過來的(這個結論先記下,後面我會畫圖喚醒你的記憶)

繼續往下走

<code>processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
/<code>
別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

processDispatchResult.png

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

render.png

<code>//這裡決定究竟是轉發還是重定向,或者說變成其他視圖
view.render(mv.getModelInternal(), request, response);
/<code>
別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

render.png

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

renderMergedOutputModel.png

通過這個方法把請求路徑傳進來

<code>protected RequestDispatcher getRequestDispatcher(HttpServletRequest request, String path) {
return request.getRequestDispatcher(path);
}
/<code>

先拿到RequestDispatcher對象,最終再去調用forward,其實底層還是servlet的內容

<code>rd.forward(request, response);
/<code>

繼續往下走

<code>mappedHandler.triggerAfterCompletion(request, response, null);
/<code>
別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

triggerAfterCompletion.png

好了,由applyPreHandle、applyPostHandle、triggerAfterCompletion、這三個方法可以得知攔截器的執行順序,下面我用一張圖來描述

別怕,手把手帶你撕、拉、扯下SpringMVC的外衣

攔截器執行流程圖.png

寫在末尾

SpringMVC的簡單執行流程到這裡就基本結束了,但是SpringMVC的設計精髓不僅僅剛才我們所看到的這些,每一個細節上都值得我們思考,然而這個思考的過程,才是看源碼的價值所在.就舉個簡單的例子,就拿異步回調來說,iOS是通常是通過block、Android是通過interface、JavaScript是通過function,然後他們又有什麼異同,就拿Node.js來說,到處是異步編程,但是異步套異步又很容易出問題,我們又是如何解決異步變同步的問題?如果換做是iOS,我們又是怎麼做的?這些都是非常值得思考.


分享到:


相關文章: