初探DispatcherServlet#doDispatch


初探DispatcherServlet#doDispatch

寫在前面

SpringBoot其實就是SpringMVC的簡化版本,對於request的處理流程大致是一樣的, 都要經過DispatcherServlet攔截之后通過相應的Handler去尋找對應的Controller處理業務最后返回ModelAndView做視圖解析之后渲染到前端頁面。

0x01 doDispatch

首先所有請求都會經過org/springframework/web/servlet/DispatcherServlet.java,這一點也可以根據該方法注釋了解到。

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

/**
	 * 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對象重新存儲到processedRequest
		HttpServletRequest processedRequest = request;
    //處理器鏈
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;
		//獲取異步請求管理器
		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
      //最終返回的ModelAndView對象
			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 afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

0x02 前期處理

前面部分代碼是對請求的一些前期處理,從processedRequest = checkMultipart(request);開始進入對request的處理邏輯部分。

首先check該request是否為文件上傳請求,如果是則重新封裝request,不是則把傳入的request原封不動return回來

之后判斷我們的requst是否在checkMultipart方法中封裝過(即request是文件上傳請求),判斷的布爾值結果賦值給multipartRequestParsed,此值類似於flag用作后面判斷,當是文件上傳請求時在最后會清除文件上傳過程中的臨時文件。

0x02 getHandler

之后進入Handler部分,調用org/springframework/web/servlet/handler/AbstractHandlerMapping#getHandler並返回executionChain賦值給mappedHandler,如果沒找到對應的handler和攔截器就會進入if中調用noHandlerFound拋出異常。

org/springframework/web/servlet/handler/AbstractHandlerMapping#getHandlerExecutionChain實現:

簡而概之,這里返回值executionChain中封裝了2個重要的東西,之后會在doDispatch中被用到:

  1. 處理當前請求的Controller及其方法的信息
  2. 當前請求所需要的攔截器信息

0x03 getHandlerAdapter

下面調用getHandlerAdapter根據之前返回的executionChain拿到handler,再根據handler獲取適配的handlerAdapter處理器適配器

這里缺省為RequestMappingHandlerAdapter優先級最高,最終返回的也是它。

0x04 Last_Modified處理

之后處理GET和HEAD請求頭的 Last_Modified 字段。

當瀏覽器第一次發起 GET 或者 HEAD 請求時,請求的響應頭中包含一個 Last-Modified 字段,這個字段表示該資源最后一次修改時間,以后瀏覽器再次發送 GET、HEAD 請求時,都會攜帶上該字段,服務端收到該字段之后,和資源的最后一次修改時間進行對比,如果資源還沒有過期,則直接返回 304 告訴瀏覽器之前的資源還是可以繼續用的,如果資源已經過期,則服務端會返回新的資源以及新的 Last-Modified

0x04 applyPreHandler

接下來做了一個判斷,調用applyPreHandler()方法對所有的攔截器進行遍歷,如果發現攔截器的preHandle()方法返回false的話,則直接執行triggerAfterCompletion()方法,並返回false,運行停止,如果獲取的布爾類型為true,則將對interceptorIndex進行賦值為1

0x05 handle

之后是handlerAdaptor調handle,去進行對handler的一個處理

這里的chain比較復雜

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

跟進到org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java#invokeAndHandle方法,這里調用invokeFoRequest會返回returnValue,該方法會根據輸入的uri,調用相關的controller的方法獲取返回值,並將其返回給returnValue,作為待查找的模板文件名,再去傳給視圖解析器處理。(這里因為我用的Controller方法中沒有返回值,所以returnValue為null)

最終層層返回值賦值給mv

0x06 異步請求處理

下一步判斷是否需要進行異步處理請求,需要的話return掉

0x07 applyDefaultViewName

接下來applyDefaultViewName方法判斷當前視圖是否為空,如果為空,調用getDefaultViewName方法獲取ModelAndView

但是因為這里mav值為空,所以viewTemplateName會從uri中獲取,我們看下是如何處理defaultViewName的,調試之后發現最終在getViewName方法中調用transformPath對URL中的path進行了處理

重點在於第3個if中stripFilenameExtension方法

/org/springframework/util/StringUtils#stripFilenameExtension該方法會對后綴做一個清除(去掉.及其之后的內容)並將該uri返回

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

0x08 applyPostHandle

接下來調用 applyPostHandle 方法執行攔截器里邊的 postHandle 方法。

0x09 processDispatchResult

之后會進入到processDispatchResult方法,包括異常處理、渲染頁面以及執行攔截器的 afterCompletion 方法都在這里完成。該方法中第1個if會被跳過,跟進第2個if中的render方法

render方法中,首先會獲取mv對象的viewName,然后調用resolveViewName方法,resolveViewName方法最終會獲取最匹配的視圖解析器。

跟一下resolveViewName方法,這里涉及到兩個方法:1、首先通過getCandidateViews篩選出resolveViewName方法返回值不為null的視圖解析器添加到candidateViews中; 2、之后通過getBestView拿到最適配的解析器,getBestView中的邏輯是優先返回在candidateViews存在重定向動作的view,如果都不存在則根據請求頭中的Accept字段的值與candidateViews的相關順序,並判斷是否兼容來返回最適配的View

getCandidateViews:

getBestView:

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

調用renderFragment

該方法在后面首先判斷viewTemplateName是否包含::,若包含則獲取解析器,調用parseExpression方法將viewTemplateName(也就是Controller中最后return的值)構造成片段表達式(~{})並解析執行。后面就不跟了,如果是Thymeleaf還會對表達式進行預處理操作,不同的視圖解析器執行流程應該也是不一樣的。

0x10 cleanupMultipart

最后在 finally 代碼塊中判斷是否開啟了異步處理,如果開啟了,則調用相應的攔截器;如果請求是文件上傳請求,則再調用 cleanupMultipart 方法清除文件上傳過程產生的一些臨時文件。

結語

簡單調試跟了下DispatcherServlet。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM