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

zfrb2306 7年前發布 | 42K 次閱讀 Spring MVC Web框架

前言

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

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

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

SpringMVC執行流程圖.png

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

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

繼承圖.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 .

doGet.png

processRequest.png

doService.png

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

doDispatch.png

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

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;
    }

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

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

getHandler.png

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

HandlerMapping

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

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

handlerMapping.png

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

<bean name="/hello" class="權限定名"></bean>

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

HandlerExecutionChain handler = hm.getHandler(request);

HandlerExecutionChain

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

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

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

getHandlerAdapter.png

HandlerAdapter

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

HandlerAdapter.png

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

if (ha.supports(handler)) {
    return ha;
}

接著往下走

String method = request.getMethod();

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

接著往下走

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

applyPreHandle.png

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

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");
    }
}

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

繼續往下走

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

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

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

再繼續往下走

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

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

@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");
}

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

applyPostHandle.png

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

繼續往下走

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

processDispatchResult.png

render.png

//這里決定究竟是轉發還是重定向,或者說變成其他視圖
view.render(mv.getModelInternal(), request, response);

render.png

renderMergedOutputModel.png

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

protected RequestDispatcher getRequestDispatcher(HttpServletRequest request, String path) {
    return request.getRequestDispatcher(path);
}

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

rd.forward(request, response);

繼續往下走

mappedHandler.triggerAfterCompletion(request, response, null);

triggerAfterCompletion.png

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

攔截器執行流程圖.png

寫在末尾

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

 

 

來自:http://www.jianshu.com/p/2372548defdb

 

 本文由用戶 zfrb2306 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!