別怕,手把手帶你撕、拉、扯下SpringMVC的外衣
但是一點開源碼,頓時代碼如洪流湧入,你的內心可能是這樣的
所以我在之前別怕看源碼,一張圖搞定Mybatis的Mapper原理的時候也提到過, Mybatis的源碼相對其他框架而言比較簡單, 比較適合剛開始克服恐懼心理看源碼實戰, 由於Struts2前不久又傳出安全性問題, 所以Java開發中, 表現層框架基本都是SpringMVC,那麽我們就來撕、拉、扯下SpringMVC的神秘外衣,可以對比之前別怕,Struts2執行流程沒那麽難,本篇中會涉及到一些的Struts2、JavaWeb以及SpringMVC使用上你一些細節.
看源碼, 首先要找到入口,那麽入口在哪? 從流程圖我們就可以看出, DispatcherServlet就是入口核心類(其實從SpringMVC的配置文件也可以得知),但是這裏面有這麽多方法,我們又知道哪個方法才是入口?我們先來看一下DispatcherServlet的繼承圖
從這裏就可以看出, 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.
doDispatch,從字面理解就知道,這個方法是分發請求的,核心的邏輯都在這裏
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;}
下面高潮來了mappedHandler = getHandler(processedRequest);
從單詞HandlerExecutionChain就知道,這個是處理執行鏈
HandlerMapping
HandlerMapping 就是請求處理映射器,它能根據不同的請求,選擇最合適的handle(自己編寫的控制器),請求處理映射器可以配置多個,誰最先匹配執行誰,
那麽這個for…in它在遍歷些什麽東西呢?其實這個在DispatcherServlet文件中已經有配置了
其實這個就是包裝了不同的Mapping來判斷是通過BeanNameUrl的方式還是Annotation的方式來配置,那什麽是BeanNameUrl的方式呢?就是我們平時在xml文件中配置的
通過這個,把request傳進入得到HandlerExecutionChain
HandlerExecutionChain handler = hm.getHandler(request);
HandlerExecutionChain
HandlerExecutionChain(處理執行鏈)包含兩部分內容,一部分是請求對應的控制器,一部分是攔截器,真正執行handle之前,有一系列操作,例如數據轉換,格式化,數據驗證這些,都是由攔截器來做的
另外需要註意的是,假如你自定義了n個攔截器,會發現HandlerExecutionChain會有n+1個攔截器,說明有一個是他內部有的,從這裏我們可以知道它的執行順序,比如這裏要先執行攔截器,再執行我們控制器,所以這個東西被稱為處理執行鏈
下面又來到了第二波高潮(這個時候那些嘴上說不要的同學,身體還是要很誠實的繼續往下看),HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
HandlerAdapter
HandlerAdapter(處理適配器)這個翻譯成中文可能比較low,但是從名稱我們就可以得知,這個是用來執行handler(控制器),那這個for…in究竟在遍歷些什麽呢?其實這個在配置文件中也是有配置好的了
這裏是判斷handle適不適合這個RequestMappingHandleAdapter,適合就返回
if (ha.supports(handler)) {
return ha;
}
接著往下走
String method = request.getMethod();
這個方法是獲取方法類型的,那麽這個get和post請求有什麽區別呢?get請求是有一個緩存的,但是post請求是不會有的
接著往下走
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
在這裏我們可以回憶一下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”)
br/>從註釋上看,這裏去調用handle的方法,這個方法會做很多事情,比如之前提到的參數自動註入就是在這個步驟做的,這個步驟層級結構太深,篇幅有限,暫時不探討,這個時候把斷點打到控制器的方法上
@RequestMapping(“/test”)
model.addAttribute(“msg”, “Hello Toby”);
return “hello”;
}
再繼續往下走
//默認視圖名稱
applyDefaultViewName(request, mv);這個默認的視圖名稱又什麽用呢?我們在使用上是不是遇到過直接返回Model但是沒有View的情況,例如
@RequestMapping(“/value2”)
br/>這個默認的視圖名稱又什麽用呢?我們在使用上是不是遇到過直接返回Model但是沒有View的情況,例如
@RequestMapping(“/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);
從這裏我們就知道的執行順序是反過來的(這個結論先記下,後面我會畫圖喚醒你的記憶)
繼續往下走
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
//這裏決定究竟是轉發還是重定向,或者說變成其他視圖
view.render(mv.getModelInternal(), request, response);
通過這個方法把請求路徑傳進來
protected RequestDispatcher getRequestDispatcher(HttpServletRequest request, String path) {
return request.getRequestDispatcher(path);
}
先拿到RequestDispatcher對象,最終再去調用forward,其實底層還是servlet的內容
rd.forward(request, response);
繼續往下走
mappedHandler.triggerAfterCompletion(request, response, null);
好了,由applyPreHandle、applyPostHandle、triggerAfterCompletion、這三個方法可以得知攔截器的執行順序,下面我用一張圖來描述
寫在末尾
SpringMVC的簡單執行流程到這裏就基本結束了,但是SpringMVC的設計精髓不僅僅剛才我們所看到的這些,每一個細節上都值得我們思考,然而這個思考的過程,才是看源碼的價值所在.就舉個簡單的例子,就拿異步回調來說,iOS是通常是通過block、Android是通過interface、JavaScript是通過function,然後他們又有什麽異同,就拿Node.js來說,到處是異步編程,但是異步套異步又很容易出問題,我們又是如何解決異步變同步的問題?如果換做是iOS,我們又是怎麽做的?這些都是非常值得思考.
如果你也想在IT行業拿高薪,可以參加我們的訓練營課程,選擇最適合自己的課程學習,技術大牛親授,7個月後,進入名企拿高薪。我們的課程內容有:Java工程化、高性能及分布式、高性能、深入淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點。如果你想拿高薪的,想學習的,想就業前景好的,想跟別人競爭能取得優勢的,想進阿裏面試但擔心面試不過的,你都可以來,群號為: 454377428
註:加群要求
1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。
2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。
3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。
4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。
5.阿裏Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!
6.小號或者小白之類加群一律不給過,謝謝。
目標已經有了,下面就看行動了!記住:學習永遠是自己的事情,你不學時間也不會多,你學了有時候卻能夠使用自己學到的知識換得更多自由自在的美好時光!時間是生命的基本組成部分,也是萬物存在的根本尺度,我們的時間在那裏我們的生活就在那裏!我們價值也將在那裏提升或消弭!Java程序員,加油吧
別怕,手把手帶你撕、拉、扯下SpringMVC的外衣