Spring MVC簡要處理過程

1. 引言

在這裏簡單簡單分析一個請求在Spring MVC中的處理過程

2. HttpServletBean

HttpServletBean主要參與了創建工作,並沒有涉及請求的處理

3. FrameworkServlet

首先從Servlet接口的service方法開始,在FrameworkServlet中重寫了service、doGet、doPost、doPut、doDelete、doOptions、doTrace方法等,所有這些方法都交給了processRequest方法進行統一處理。以doGet的代碼爲例

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

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

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

		processRequest(request, response);
	}

這樣做的好處是開發者如果對DispatcherServlet有特殊對定製需求可以覆蓋doGet、doPost等方法,但是一般不必要修改內部。讓我們來看一下processReqeust方法,他是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;
        //通過ContextHolder獲得請求參數
		LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
		LocaleContext localeContext = buildLocaleContext(request);

		RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
		asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

		initContextHolders(request, localeContext, requestAttributes);

		try {
            //真正的處理交給doService模版方法實現,在FrameworkServlet中是空實現,由DispatcherServlet重寫
			doService(request, response);
		}
		catch (ServletException | IOException ex) {
			failureCause = ex;
			throw ex;
		}
		catch (Throwable ex) {
			failureCause = ex;
			throw new NestedServletException("Request processing failed", ex);
		}

		finally {
			resetContextHolders(request, previousLocaleContext, previousAttributes);
			if (requestAttributes != null) {
				requestAttributes.requestCompleted();
			}
			logResult(request, response, failureCause, asyncManager);
            //事件發佈
			publishRequestHandledEvent(request, response, startTime, failureCause);
		}
	}

processRequest自己主要做了兩件事:

  1. 對LocaleContext和RequestAttributes的設置和恢復
  2. 處理完後發佈ServletRequestHandledEvent

3.1 LocaleContextHolder和RequestContextHolder

LocaleContext存放着本地化信息,RequestAttributes是Spring的一個接口,通過他的get/set/removeAttribute根據scope判斷操作是request還是session。

這裏順便提一下LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext()其內部如下

	/**
	 * Return the LocaleContext associated with the current thread, if any.
	 * @return the current LocaleContext, or {@code null} if none
	 */
	@Nullable
	public static LocaleContext getLocaleContext() {
		LocaleContext localeContext = localeContextHolder.get();
		if (localeContext == null) {
			localeContext = inheritableLocaleContextHolder.get();
		}
		return localeContext;
	}

這裏用到了ThreadLocal,通過他每個線程可以獨立保存自己的內容,其核心原理是Thread內部有一個屬性爲ThreadLocal.ThreadLocalMap threadLocals,當ThreadLocal在get/set時首先拿到線程當threadLocals,然後以ThreadLocal實例作爲key,所要保存的值爲value,put進去,這樣就將具體當值保存在線程自身上面。
LocaleContextHolder和RequestContextHolder可以方便在service層沒有servlet時直接獲得了Locale和RequestAttributes,順便打個斷點看了一下他們是在什麼時候把這幾個屬性注入的,發現是在RequestContextFilter中的doFilterInternal方法裏的initContextHolders(request, attributes)方法裏

	@Override
	protected void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		ServletRequestAttributes attributes = new ServletRequestAttributes(request, response);
        //注入Locale和RequestAttributes
		initContextHolders(request, attributes);

		try {
			filterChain.doFilter(request, response);
		}
		finally {
			resetContextHolders();
			if (logger.isTraceEnabled()) {
				logger.trace("Cleared thread-bound request context: " + request);
			}
			attributes.requestCompleted();
		}
	}

	private void initContextHolders(HttpServletRequest request, ServletRequestAttributes requestAttributes) {
		LocaleContextHolder.setLocale(request.getLocale(), this.threadContextInheritable);
		RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
		if (logger.isTraceEnabled()) {
			logger.trace("Bound request context to thread: " + request);
		}
	}

3.2 事件發佈

publishRequestHandledEvent發佈一個ServletRequestHandledEvent消息,可以通過監聽這個事件記錄一些事情,通過實現接口ApplicationListenner

4. DispatcherServlet

4.1 doService

DispatcherServlet是Spring MVC最核心的類,整個處理過程的頂層設計都在這裏。其處理方法的入口在doService在其內部又交給了doDispatcher進行具體的處理,doService方法大概過程是首先判斷是否包含請求,如果是則對request的Attribut做個快照並設置一些屬性,等doDispatch處理完之後進行還原。代碼如下:

/**
	 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
	 * for the actual dispatching.
	 */
	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		logRequest(request);

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		Map<String, Object> attributesSnapshot = null;
		if (WebUtils.isIncludeRequest(request)) {
			attributesSnapshot = new HashMap<>();
			Enumeration<?> attrNames = request.getAttributeNames();
			while (attrNames.hasMoreElements()) {
				String attrName = (String) attrNames.nextElement();
				if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
					attributesSnapshot.put(attrName, request.getAttribute(attrName));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		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());

        //實現redirect時的數據保存
		if (this.flashMapManager != null) {
			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()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
		}
	}

其中的flashmap方便direct時候的保存一些數據而不用暴露在url中,我們只需要在redirect之前將需要傳遞的參數寫入OUTPUT_FLASH_MAP_ATTRIBUTE

((FlashMap)((ServletReqeustAttributes)(RequestContextHolder.getRequestAttributes())
    .getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE))).put("name","littlemotor");

之後就會被設置到INPUT_FLASH_MAP_ATTRIBUTE屬性裏,然後再放到model裏,當然Spring提供了更方便的方法,在控制器中通過注入RedirectAttributes實例,通過addAttribute(key,value),addFlashAttribute(key,value),可以達到同樣的效果,第一種是在redirect時參數拼接到url中,第二種是在redirect時參數保存到flashMap中,例如下面的方式:

@PostMapping("submit")
public String submit(RedirectAttribute attr){
    ((FlashMap)((ServletReqeustAttributes)(RequestContextHolder.getRequestAttributes())
    .getAttribute(DispatcherServlet.OUTPUT_FLASH_MAP_ATTRIBUTE))).put("id","xxx");
    //與上面的等效
    attr.addFlashAttribute("id","xxx");
    //zh-cn會拼接到uri上面
    attr.add.addAttribute("local","zh-cn");
    return "redirect:uri";
}

inputFlashMap用於保存上次請求中轉發過來的屬性,outputFlashMap用於保存本次請求中需要轉發的屬性,FlashMapManager用於管理他們。

4.2 doDispatch

doDispatch方法也比較簡介,其最核心的代碼就4句

  1. 根據request找到Handler
  2. 根據Handler找到對應的HandlerAdapter
  3. 用HandlerAdapter處理Handler
  4. 調用processDispatchResult方法處理上面每一步得到的結果
/**
	 * 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 {
		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);

				// Determine handler for the current request.
                //根據request找到Handler
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
                //根據Handler找到對應的HandlerAdapter
				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.
                //用HandlerAdapter處理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);
				}
			}
		}
	}

首先需要理解一下三個概念:

  1. Handler:每一個被@RequestMapping標註的方法就是一個Handler,用於處理實際請求
  2. HandlerMapping:用來查找Handler
  3. HandlerAdapter:他是一個適配器,因爲Handler的形式是靈活的,可以是類也可以是方法,而servlet的處理方式是固定的,都是以reqeust和response爲參數的方法

doDispatcher大體可以分爲兩部分:處理請求和渲染頁面,開頭定義了幾個變量:

  • HttpServletRequest processedRequest,實際處理時所用的request,如果不是上傳請求則直接使用接收到的request,否則封裝爲上傳類型的MultipartHttpServletRequest
  • HandlerExecutionChain mappedHandler:處理請求的處理器鏈(包含處理器和對應的Interceptor)
  • boolean multipartRequestParsed: 是不是上傳請求的標誌
  • ModelAndView mv:封裝Model和View的容器
  • Excetion dispatchException:處理請求過程中拋出的異常,並不包含渲染過程中拋出的異常

4.2.1 getHandler

getHandler方法獲取Handler處理器鏈,其中使用到了HandlerMapping返回值爲HandlerExecutionChain類型,其中有與當前request相匹配的HandlerInterceptor和Handler,執行時依次執行Interceptor的PreHandler,然後執行Handler,返回時執行Interceptor的postHandler方法。

	/**
	 * Return the HandlerExecutionChain for this request.
	 * <p>Tries all handler mappings in order.
	 * @param request current HTTP request
	 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
	 */
	@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

4.2.2 getHandlerAdapter

通過HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());獲取HandlerAdapter,從類型名就可以知道這是一個適配器模式,mappedHandler.getHandler()返回的是object類型,因爲handler的類型是多樣的,在下面的getHandlerAdapter方法中通過遍歷handlerAdapters,找到適配的atapter並返回。

/**
	 * Return the HandlerAdapter for this handler object.
	 * @param handler the handler object to find an adapter for
	 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
	 */
	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			for (HandlerAdapter adapter : this.handlerAdapters) {
				if (adapter.supports(handler)) {
					return adapter;
				}
			}
		}
		throw new ServletException("No adapter for handler [" + handler +
				"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
	}

之後分別進行preHandle方法,HandlerAdapter使用Handler處理請求並得到modelAndView,如果需要異步直接返回,否則執行postHandle方法,這樣內層的try塊就執行完成了。

4.2.3 processDispatchResult

processDispatchResult處理前面返回的結果,包括處理異常,渲染頁面,觸發Interceptor的afterCompletion方法三部分內容。

/**
	 * Handle the result of handler selection and handler invocation, which is
	 * either a ModelAndView or an Exception to be resolved to a ModelAndView.
	 */
	private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
			@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
			@Nullable 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.isTraceEnabled()) {
				logger.trace("No view rendering, null ModelAndView returned.");
			}
		}

		//如果啓動了異步處理則返回
		if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
			// Concurrent handling started during a forward
			return;
		}

		//發出請求處理完成的通知,觸發Interceptor的afterCompletion
		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

4.2.4 異常捕獲

  • 內層捕獲的是對請求進行處理的過程中拋出的異常,即被捕獲並傳遞給dispatcherException變量的異常,然後由processDispatchResult方法進行處理。
  • 外層主要是處理渲染頁面時拋出的異常,主要處理processDispatchResult方法拋出的異常。

4.2.5 finally

在最後的finally中判斷是否請求啓動了異步處理,如果啓動了則調用相應的異步處理攔截器,否則如果是上傳請求則刪除上傳請求過程中產生的臨時資源。

5. 小結

doDispatch流程

整個過程大致如下:

  1. HttpServletBean沒有參與實際請求處理
  2. FrameworkServlet將不同的請求合併到processRequest方法統一處理,processReqeust方法做了三件事情:
    • 調用doService模板方法處理請求
    • 將當前請求的LocaleContext和ServletReqeustAttribes在處理請求前設置到LocaleContextHolder和ReqeustContextHolder,並在請求處理完成後恢復
    • 請求處理完後發佈ServletRequestEvent消息
  3. DispatcherServlet:doService設置了一些屬性並將請求交給doDispatch方法具體處理。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章