1. 程式人生 > >springmvc處理請求詳解與原始碼分析

springmvc處理請求詳解與原始碼分析

一、Dispatchservlet繼承體系

在這裡插入圖片描述

在我上一篇部落格中,我主要分析了springmvc體系的建立過程,主要是上圖中DispatcherServlet,它的父類FrameworkServlet,及HttpServletBean的建立過程,詳情可至 springmvc的建立過程詳解 檢視,配合本文食用效果更佳。

本篇文章,將主要講解springmvc自頂而下是怎麼處理請求的,我將詳細的分析處理請求的全過程及涉及到的知識點,這樣大家就可以明白從servlet容器將請求交給springmvc一直到DispatcherServlet具體處理請求之前都做了些什麼,最後再將重點分析springmvc中最核心的處理方法----doDispatch

的結構。

二、HttpServletBean處理請求過程

HttpServletBean主要參與的是建立過程,並沒有涉及到請求的處理。我特地列出來是為了告訴大家,這個類是沒有去處理請求的

三、FrameworkServlet處理請求過程

大家都知道,servlet處理請求的過程首先是從service方法開始的,然後在HttpServlet的service方法中,將不同的請求方式路由到各自的方法中進行處理。FrameworkServlet不但重寫了HttpServlet的service方法,而且重寫了具體的除doHead之外的所有處理方法。它在service方法中加入了對PATCH型別請求的處理。其他型別的請求直接交給了父類處理

,下面是service方法的原始碼:

/**
	 * Override the parent class implementation in order to intercept PATCH requests.
	 */
	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
		if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
			processRequest(request, response);
		}
		else {
			super.service(request, response);
		}
	}

在FrameworkServlet中有2個屬性:dispatchOptionsRequestdispatchTraceRequest,可以通過設定這2個引數來決定是FrameworkServlet自己處理doOptions和doTrace方法還是父類來處理(預設是父類來處理),而doGet、doPost、doPut、doDelete方法都是FrameworkServlet自己處理(因為重寫了HttpServlet中的方法),而所有需要自己處理的方法都交給了processRequest方法處理:

doOptions():通過dispatchOptionsRequest 來判斷是自己處理還是由父類處理

@Override
	protected void doOptions(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
			
        //dispatchOptionsRequest 預設為false
		if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
			processRequest(request, response);
			if (response.containsHeader("Allow")) {
				// Proper OPTIONS response coming from a handler - we're done.
				return;
			}
		}
		//預設是由父類處理
		super.doOptions(request, new HttpServletResponseWrapper(response) {
			@Override
			public void setHeader(String name, String value) {
				if ("Allow".equals(name)) {
					value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();
				}
				super.setHeader(name, value);
			}
		});
	}

doGet():走自己類(FrameworkServlet)的處理方法

@Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}

我們可以看出,FrameworkServlet處理請求的方法與HttpServlet處理請求的方法剛好相反,它將所有的請求全部合併到了processRequest方法中

下面就來看一下processRequest方法,它是FrameworkServlet類處理請求的過程中最核心的方法:

/**
	 * Process this request, publishing an event regardless of the outcome.
	 * <p>The actual event handling is performed by the abstract
	 * {@link #doService} template method.
	 */
	protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		long startTime = System.currentTimeMillis();
		Throwable failureCause = null;
		
			//獲取LocaleContextHolder中原來儲存的LocaleContext
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
			//獲取當前請求的LocaleContext
		LocaleContext localeContext = buildLocaleContext(request);
			//獲取RequestContextHolder原來儲存的RequestAttributes
		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
			//獲取當前請求的RequestAttributes
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
			//將當前請求的LocaleContext和ServletRequestAttributes設定到LocaleContextHolder和RequestContextHolder中
		initContextHolders(request, localeContext, requestAttributes);

		try {
			//實際處理請求的入口
			doService(request, response);
		}
		catch (ServletException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (IOException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
			//恢復原來的LocaleContext和ServletRequestAttributes到LocaleContextHolder和			   RequestContextHolder中
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				requestAttributes.requestCompleted();
			}

			if (logger.isDebugEnabled()) {
				if (failureCause != null) {
					this.logger.debug("Could not complete request", failureCause);
				}
				else {
					if (asyncManager.isConcurrentHandlingStarted()) {
						logger.debug("Leaving response open for concurrent processing");
					}
					else {
						this.logger.debug("Successfully completed request");
					}
				}
			}
			//釋出servletRequestHandledEvent訊息
			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

processRequest方法中核心語句是doService方法,這是一個模板方法,在DispatcherServlet中具體實現,也是整個springmvc中處理請求的核心方法,這裡我們之後再說。而在doService方法執行前還做了一些事情(有點裝飾模式的味道):

  1. 獲取LocaleContextHolder和RequestContextHolder中原來儲存的LocaleContext與RequestAttributes
  2. 對非同步的請求做出一些處理
  3. 獲取當前請求的LocaleContext和RequestAttributes,一併設定到LocaleContextHolder和RequestContextHolder中

最後,處理完請求之後(finally),通過resetContextHolders方法又恢復到之前的樣子,並呼叫publishRequestHandledEvent釋出了一個ServletRequestHandlerEvent型別的訊息。

在這裡涉及到了2個介面類:LocaleContext,RequestAttributes,我簡單介紹一下這2個類:

  1. LocaleContext:這個類中主要是儲存著本地化資訊,如zh-cn等
  2. RequestAttributes:這個類裡面封裝了request、response、session,並提供了get方法,可以直接獲取

四、DispatcherServlet處理請求過程

通過之前對FrameworkServlet的分析,我們可以知道,DispatcherServlet處理請求的入口方法是doService,不過doService方法並沒有直接處理請求,而是交給了doDispatch方法進行處理,而在這之前,doService方法還做了其他一些事情:

  1. 判斷是否為include請求,如果是,則對request的Attribute進行備份
  2. 對request設定了一些屬性

下面是原始碼:

@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		if (logger.isDebugEnabled()) {
			String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
			logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
					" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
		}
		
//***************如果是include請求,則對request進行快照備份***********************
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<String, Object>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

//****************************** 對request設定一些屬性****************************.
		request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
		request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
		request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
		request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

		FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
		if (inputFlashMap != null) {
			request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
		}
		request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
		request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

		try {
			doDispatch(request, response);
		}
		finally {
			if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
				// 還原快照備份(如果是include請求)
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

在處理完請求之後,如果請求是include請求,則會還原request的快照屬性
————————————————————————————————————————————————————————————
在對request設定的屬性中,前面的四個屬性:WebApplicationContext、localeResolver、themeResolver、ThemeSource,我會在之後分析。

但後面request設定的三個屬性都與flashMap有關:flashMap主要用於redirect的時候傳遞引數,比如,為了避免重複提交表單,可以在post請求之後redirect到一個get的請求,這樣即使使用者重新整理瀏覽器也不會造成重複提交表單。不過這裡有個問題,如果想要在使用者提交表單之後轉到該訂單的詳情頁面,就必須要在傳遞一些引數,而redirect本身是無法傳遞引數的,只有通過在url之後加入引數,但是這種方法又有長度限制,這時候就可以使用flashMap來傳遞引數了,flashMap的具體使用方法可以看我的這篇文章,我這裡就不再贅述了:

————————————————————————————小小的分割線————————————————————————————

下面,我們重點分析doDispatch方法的結構,它是一個請求在springmvc中要面對的最終大boss

五、doDispatch

先貼原始碼,我在上面加了文字註釋,如果能直接通過IDE看原始碼,就直接跳到後面的文字部分吧:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

		try {
			ModelAndView mv = null;
			Exception dispatchException = null;

			try {
		//*******************檢查是不是上傳請求**************************
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

		// *******************根據request找到Handler*******************
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null || mappedHandler.getHandler() == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

		//*******************根據Handler找到HandlerAdapter*******************
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

		// *******************處理get、head請求的Last-Modified*******************
				String method = request.getMethod();
				boolean isGet = "GET".equals(method);
				if (isGet || "HEAD".equals(method)) {
					long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
					if (logger.isDebugEnabled()) {
						logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
					}
					if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
						return;
					}
				}

		//*******************執行攔截器的PreHandler方法*******************
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

		// *******************使用Handler處理請求*******************
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

		//*******************如果需要非同步處理,則直接返回*******************
				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}
		//*******************當view為空時(Controller方法返回void),根據request設定預設的view**************
				applyDefaultViewName(processedRequest, mv);
		//*******************執行攔截器的PostHandle方法*******************
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
	//*******************這裡是spring4.3之後新加的一句話*******************
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
   //*******************處理返回結果,包括異常、渲染頁面、觸發攔截器的afterCompletion方法*******************
			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 {
	 // *******************刪除上傳請求的資源*******************
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

doDispatch方法的結構也還算簡潔,其中最核心的程式碼就4句:
***mappedHandler = getHandler(processedRequest);

***HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

***mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

它們分別對應的任務是:

  1. 根據request找到handler
  2. 根據handler找到HandlerAdapter
  3. 用HandlerAdapter處理handler
  4. 呼叫processDispatchResult方法處理返回的結果(包括渲染頁面並返回給使用者)

這裡涉及的三個概念HandlerMapping、Handler和HandlerAdapter,這對理解springmvc十分重要,如果對這三個概念還是有點迷迷糊糊的話,可以先看下我的這篇文章:HandlerMapping、Handler和HandlerAdapter的介紹

—————————————————————————高能分割線———————————————————————————————

下面,我將詳細地分析doDispatch方法,若只想瞭解大體流程的話,看上面原始碼的文字註釋也能大體明白,如果你能耐心往下看的話就來吧!

doDispatch方法大體可以分為2個部分:處理請求與處理請求結果,開頭部分定義了幾個變數,我簡單介紹一下:

  1. HttpServletRequest processedRequest:實際處理時的request,如果不是上傳請求則直接使用傳入的request,如果是,就將這個變數封裝為上傳型別request。
  2. HandlerExecutionChain mappedHandler:處理請求的處理器鏈,這個類中封裝了處理器與對應的攔截器
public class HandlerExecutionChain {

	private static final Log logger = LogFactory.getLog(HandlerExecutionChain.class);

	private final Object handler;

	private HandlerInterceptor[] interceptors;

	private List<HandlerInterceptor> interceptorList;

	private int interceptorIndex = -1;
	
	.......................................
	
}
  1. boolean multipartRequestParsed:是否為上傳請求
  2. ModelAndView mv:封裝了model與view的容器
  3. Exception dispatchException:處理請求過程中丟擲的異常,注意,這裡並不包括渲染頁面時發生的異常。

處理請求

  1. 先檢查是不是上傳的請求,並將標誌賦給multipartRequestParsed。
  2. 根據request獲取Handler處理器鏈,其中包含著當前請求的Handler與攔截器,我們來看看getHandler的原始碼:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		for (HandlerMapping hm : this.handlerMappings) {
			if (logger.isTraceEnabled()) {
				logger.trace(
						"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
			}
			HandlerExecutionChain handler = hm.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}

這個方法中獲取handler的方法就是hm.getHandler(request),其中hm就是dispatchServlet初始化的時候,通過applicationContext傳入進來的(initHandlerMappings),還算比較好理解

  1. 根據Handler查詢HandlerAdapter,這裡的原理跟上面類似,不過多了一個判斷條件:判斷初始化時註冊的HandlerAdapter是否支援該Handler
	.........
	
	if (ha.supports(handler)) {
					return ha;
				}
				
	.........
  1. 接下來處理GET、HEAD請求的Last-Modified:當瀏覽器第一次跟伺服器請求資源(GET\HEAD)的時候,伺服器會在返回的請求頭裡加入一個Last-Modified屬性,代表資源最後是什麼時候修改的。瀏覽器以後傳送請求時,會將這個屬性同時傳送給伺服器,伺服器會將傳過來的值與實際資源修改的時間做對比,如果資源過期則返回新的資源與新的Last-Modified,如果沒有過期,則直接使用之前瀏覽器快取的資源。
  2. 執行該請求的攔截器中的preHandler方法
  3. HandlerAdapter使用Handler處理請求,這裡就是controller層寫的程式碼執行的地方了
  4. 當view為空時,設定預設的view:這裡對應的情況就是controller層的方法返回void值。
  5. 執行該請求的攔截器中的postHandler方法

處理請求結果

這裡的處理結果包括處理異常、渲染頁面、與觸發攔截器的AfterCompletion方法,處理的方法就是processDispatchResult(),下面是原始碼,並附上文字註釋:

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

		boolean errorView = false;
	
	//*********************如果處理“請求”的過程中有異常,則處理異常************************
		if (exception != null) {
			if (exception instanceof ModelAndViewDefiningException) {
				logger.debug("ModelAndViewDefiningException encountered", exception);
				mv = ((ModelAndViewDefiningException) exception).getModelAndView();
			}
			else {
				Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}

	// ******************************渲染頁面*************************************
		if (mv != null && !mv.wasCleared()) {
			render(mv, request, response);
			if (errorView) {
				WebUtils.clearErrorRequestAttributes(request);
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
						"': assuming HandlerAdapter completed request handling");
			}
		}

		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}
		
//***************************觸發攔截器的AfterCompletion************************
		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

我們先說一下doDispatch方法的處理異常的方式,在這裡,異常共分為2層異常,內層是處理請求時發生的異常,外層是處理結果時發生的異常。內層異常在會設定到dispatchException中,然後傳入processDispatchResult方法,在這裡面處理;而外層異常則是processDispatchResult方法中發生的異常。processDispatchResult處理異常的方法則是直接將異常設定到view裡面

渲染頁面的具體方法則是render方法,渲染之後則通過triggerAfterCompletion呼叫攔截器的afterCompletion方法,至此,processDispatchResult方法結束

再返回doDispatch方法中,如果請求時非同步請求,則呼叫相應的非同步處理的攔截器,否則,進入下一步,如果是上傳請求則刪除上傳的臨時資源。

doDispatch方法就分析完了,我們回顧一下整個方法的流程,發現前一部分處理請求是關聯著Controller層的,中間處理請求結果時是關聯著View層的,而Model層則貫穿全場,不愧叫springmvc啊!

最後,我附上用startUML畫的流程圖,左邊是攔截器,中間是doDispatch方法的流程,右邊是用到的相關元件,這裡一共用到了8個元件,除了FlashMapManager。

doDispatch流程圖

———————————原創文章,轉載請說明—————————————————