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

前言

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

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

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

SpringMVC執(zhí)行流程圖.png

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

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

繼承圖.png

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

doGet.png

processRequest.png

doService.png

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

doDispatch.png

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

protectedHttpServletRequestcheckMultipart(HttpServletRequest request)throwsMultipartException{//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");? ? ? ? ? ? }elseif(request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE)instanceofMultipartException) {? ? ? ? ? ? ? ? logger.debug("Multipart resolution failed for current request before - "+"skipping re-resolution for undisturbed error rendering");? ? ? ? ? ? }else{// 如果是二進制的話,把request包裝一層,返回MultipartHttpServletRequestreturnthis.multipartResolver.resolveMultipart(request);? ? ? ? ? ? }? ? ? ? }// If not returned before: return original request.returnrequest;? ? }

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

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

getHandler.png

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

HandlerMapping

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

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

handlerMapping.png

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

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

HandlerExecutionChain handler = hm.getHandler(request);

HandlerExecutionChain

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

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

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

getHandlerAdapter.png

HandlerAdapter

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

HandlerAdapter.png

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

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

接著往下走

String method = request.getMethod();

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

接著往下走

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

applyPreHandle.png

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

publicclassMyInterceptorimplementsHandlerInterceptor{//表示控制器方法執(zhí)行之前調(diào)用的方法,返回結(jié)果為boolean,如果為true,表示放行,如果為false,表示攔截publicbooleanpreHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)throwsException{? ? ? ? System.out.println("MyInterceptor.preHandle");returntrue;? ? }//控制器執(zhí)行完方法之后,視圖結(jié)合之前publicvoidpostHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView)throwsException{? ? ? ? System.out.println("MyInterceptor.postHandle");? ? }//視圖結(jié)合完成之后調(diào)用的方法publicvoidafterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e)throwsException{? ? ? ? System.out.println("MyInterceptor.afterCompletion");? ? }}

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

繼續(xù)往下走

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

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

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

再繼續(xù)往下走

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

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

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

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

applyPostHandle.png

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

繼續(xù)往下走

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

processDispatchResult.png

render.png

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

render.png

renderMergedOutputModel.png

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

protectedRequestDispatchergetRequestDispatcher(HttpServletRequest request, String path){returnrequest.getRequestDispatcher(path);}

先拿到RequestDispatcher對象,最終再去調(diào)用forward,其實底層還是servlet的內(nèi)容

rd.forward(request, response);

繼續(xù)往下走

mappedHandler.triggerAfterCompletion(request, response,null);

triggerAfterCompletion.png

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

攔截器執(zhí)行流程圖.png

寫在末尾:如果你在學(xué)習(xí)Java的過程中或者在工作中遇到什么問題都可以來群里提問,阿里Java高級大牛直播講解知識點,分享知識,多年工作經(jīng)驗的梳理和總結(jié),帶著大家全面、科學(xué)地建立自己的技術(shù)體系和技術(shù)認知!可以加群找我要課堂鏈接 注意:是免費的 沒有開發(fā)經(jīng)驗誤入哦! 非喜勿入!?學(xué)習(xí)交流QQ群:478052716

SpringMVC的簡單執(zhí)行流程到這里就基本結(jié)束了,但是SpringMVC的設(shè)計精髓不僅僅剛才我們所看到的這些,每一個細節(jié)上都值得我們思考,然而這個思考的過程,才是看源碼的價值所在.就舉個簡單的例子,就拿異步回調(diào)來說,iOS是通常是通過block、Android是通過interface、JavaScript是通過function,然后他們又有什么異同,就拿Node.js來說,到處是異步編程,但是異步套異步又很容易出問題,我們又是如何解決異步變同步的問題?如果換做是iOS,我們又是怎么做的?這些都是非常值得思考.時間比較倉促,如果文中有不對的地方,還望大家斧正.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容