日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

初探DispatcherServlet#doDispatch

發(fā)布時(shí)間:2025/1/21 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 初探DispatcherServlet#doDispatch 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

寫在前面

SpringBoot其實(shí)就是SpringMVC的簡(jiǎn)化版本,對(duì)于request的處理流程大致是一樣的, 都要經(jīng)過(guò)DispatcherServlet攔截之后通過(guò)相應(yīng)的Handler去尋找對(duì)應(yīng)的Controller處理業(yè)務(wù)最后返回ModelAndView做視圖解析之后渲染到前端頁(yè)面。

0x01 doDispatch

首先所有請(qǐng)求都會(huì)經(jīng)過(guò)org/springframework/web/servlet/DispatcherServlet.java,這一點(diǎn)也可以根據(jù)該方法注釋了解到。

我們的請(qǐng)求會(huì)先進(jìn)入到DispatcherServlet#doService方法中,在doService中調(diào)用了doDispatch,而doDispatch是實(shí)現(xiàn)大部分處理request邏輯的地方,大致可分為請(qǐng)求處理(如尋找相應(yīng)controller,獲取ModelAndView,resolveView視圖解析等)和頁(yè)面渲染,下面是該方法代碼。

/*** Process the actual dispatching to the handler.* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters* to find the first that supports the handler class.* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers* themselves to decide which methods are acceptable.* @param request current HTTP request* @param response current HTTP response* @throws Exception in case of any kind of processing failure*/protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {//將request對(duì)象重新存儲(chǔ)到processedRequestHttpServletRequest processedRequest = request;//處理器鏈HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;//獲取異步請(qǐng)求管理器WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {//最終返回的ModelAndView對(duì)象ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.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 (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 afterCompletionif (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}}else {// Clean up any resources used by a multipart request.if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

0x02 前期處理

前面部分代碼是對(duì)請(qǐng)求的一些前期處理,從processedRequest = checkMultipart(request);開(kāi)始進(jìn)入對(duì)request的處理邏輯部分。

首先check該request是否為文件上傳請(qǐng)求,如果是則重新封裝request,不是則把傳入的request原封不動(dòng)return回來(lái)

之后判斷我們的requst是否在checkMultipart方法中封裝過(guò)(即request是文件上傳請(qǐng)求),判斷的布爾值結(jié)果賦值給multipartRequestParsed,此值類似于flag用作后面判斷,當(dāng)是文件上傳請(qǐng)求時(shí)在最后會(huì)清除文件上傳過(guò)程中的臨時(shí)文件。

0x02 getHandler

之后進(jìn)入Handler部分,調(diào)用org/springframework/web/servlet/handler/AbstractHandlerMapping#getHandler并返回executionChain賦值給mappedHandler,如果沒(méi)找到對(duì)應(yīng)的handler和攔截器就會(huì)進(jìn)入if中調(diào)用noHandlerFound拋出異常。

org/springframework/web/servlet/handler/AbstractHandlerMapping#getHandlerExecutionChain實(shí)現(xiàn):

簡(jiǎn)而概之,這里返回值executionChain中封裝了2個(gè)重要的東西,之后會(huì)在doDispatch中被用到:

  • 處理當(dāng)前請(qǐng)求的Controller及其方法的信息
  • 當(dāng)前請(qǐng)求所需要的攔截器信息
  • 0x03 getHandlerAdapter

    下面調(diào)用getHandlerAdapter根據(jù)之前返回的executionChain拿到handler,再根據(jù)handler獲取適配的handlerAdapter處理器適配器

    這里缺省為RequestMappingHandlerAdapter優(yōu)先級(jí)最高,最終返回的也是它。

    0x04 Last_Modified處理

    之后處理GET和HEAD請(qǐng)求頭的 Last_Modified 字段。

    當(dāng)瀏覽器第一次發(fā)起 GET 或者 HEAD 請(qǐng)求時(shí),請(qǐng)求的響應(yīng)頭中包含一個(gè) Last-Modified 字段,這個(gè)字段表示該資源最后一次修改時(shí)間,以后瀏覽器再次發(fā)送 GET、HEAD 請(qǐng)求時(shí),都會(huì)攜帶上該字段,服務(wù)端收到該字段之后,和資源的最后一次修改時(shí)間進(jìn)行對(duì)比,如果資源還沒(méi)有過(guò)期,則直接返回 304 告訴瀏覽器之前的資源還是可以繼續(xù)用的,如果資源已經(jīng)過(guò)期,則服務(wù)端會(huì)返回新的資源以及新的 Last-Modified

    0x04 applyPreHandler

    接下來(lái)做了一個(gè)判斷,調(diào)用applyPreHandler()方法對(duì)所有的攔截器進(jìn)行遍歷,如果發(fā)現(xiàn)攔截器的preHandle()方法返回false的話,則直接執(zhí)行triggerAfterCompletion()方法,并返回false,運(yùn)行停止,如果獲取的布爾類型為true,則將對(duì)interceptorIndex進(jìn)行賦值為1

    0x05 handle

    之后是handlerAdaptor調(diào)handle,去進(jìn)行對(duì)handler的一個(gè)處理

    這里的chain比較復(fù)雜

    org/springframework/web/servlet/mvc/method/AbstractHandlerMethodAdapter#handle org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter#handleInternal org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter#invokeHandlerMethod org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod#invokeAndHandle org/springframework/web/method/support/InvocableHandlerMethod#invokeForRequest

    跟進(jìn)到org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java#invokeAndHandle方法,這里調(diào)用invokeFoRequest會(huì)返回returnValue,該方法會(huì)根據(jù)輸入的uri,調(diào)用相關(guān)的controller的方法獲取返回值,并將其返回給returnValue,作為待查找的模板文件名,再去傳給視圖解析器處理。(這里因?yàn)槲矣玫腃ontroller方法中沒(méi)有返回值,所以returnValue為null)

    最終層層返回值賦值給mv

    0x06 異步請(qǐng)求處理

    下一步判斷是否需要進(jìn)行異步處理請(qǐng)求,需要的話return掉

    0x07 applyDefaultViewName

    接下來(lái)applyDefaultViewName方法判斷當(dāng)前視圖是否為空,如果為空,調(diào)用getDefaultViewName方法獲取ModelAndView

    但是因?yàn)檫@里mav值為空,所以viewTemplateName會(huì)從uri中獲取,我們看下是如何處理defaultViewName的,調(diào)試之后發(fā)現(xiàn)最終在getViewName方法中調(diào)用transformPath對(duì)URL中的path進(jìn)行了處理

    重點(diǎn)在于第3個(gè)if中stripFilenameExtension方法

    /org/springframework/util/StringUtils#stripFilenameExtension該方法會(huì)對(duì)后綴做一個(gè)清除(去掉.及其之后的內(nèi)容)并將該uri返回

    最終通過(guò)mv.setViewName(defaultViewName);將該uri賦值給mv

    0x08 applyPostHandle

    接下來(lái)調(diào)用 applyPostHandle 方法執(zhí)行攔截器里邊的 postHandle 方法。

    0x09 processDispatchResult#

    之后會(huì)進(jìn)入到processDispatchResult方法,包括異常處理、渲染頁(yè)面以及執(zhí)行攔截器的 afterCompletion 方法都在這里完成。該方法中第1個(gè)if會(huì)被跳過(guò),跟進(jìn)第2個(gè)if中的render方法

    在render方法中,首先會(huì)獲取mv對(duì)象的viewName,然后調(diào)用resolveViewName方法,resolveViewName方法最終會(huì)獲取最匹配的視圖解析器。

    跟一下resolveViewName方法,這里涉及到兩個(gè)方法:1、首先通過(guò)getCandidateViews篩選出resolveViewName方法返回值不為null的視圖解析器添加到candidateViews中; 2、之后通過(guò)getBestView拿到最適配的解析器,getBestView中的邏輯是優(yōu)先返回在candidateViews存在重定向動(dòng)作的view,如果都不存在則根據(jù)請(qǐng)求頭中的Accept字段的值與candidateViews的相關(guān)順序,并判斷是否兼容來(lái)返回最適配的View

    getCandidateViews:

    getBestView:

    這里最終返回的是ThymeleafView(不同情況會(huì)返回不同的視圖解析器,添加了Thymeleaf依賴會(huì)有ThymeleafView,也可能會(huì)有自定義的視圖解析器,返回值不唯一)之后ThymeleafView調(diào)用了render方法,繼續(xù)跟進(jìn)

    調(diào)用renderFragment

    該方法在后面首先判斷viewTemplateName是否包含::,若包含則獲取解析器,調(diào)用parseExpression方法將viewTemplateName(也就是Controller中最后return的值)構(gòu)造成片段表達(dá)式(~{})并解析執(zhí)行。后面就不跟了,如果是Thymeleaf還會(huì)對(duì)表達(dá)式進(jìn)行預(yù)處理操作,不同的視圖解析器執(zhí)行流程應(yīng)該也是不一樣的。

    0x10 cleanupMultipart

    最后在 finally 代碼塊中判斷是否開(kāi)啟了異步處理,如果開(kāi)啟了,則調(diào)用相應(yīng)的攔截器;如果請(qǐng)求是文件上傳請(qǐng)求,則再調(diào)用 cleanupMultipart 方法清除文件上傳過(guò)程產(chǎn)生的一些臨時(shí)文件。

    結(jié)語(yǔ)

    簡(jiǎn)單調(diào)試跟了下DispatcherServlet。

    總結(jié)

    以上是生活随笔為你收集整理的初探DispatcherServlet#doDispatch的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。