SpringMVC源碼剖析(四)- DispatcherServlet請求轉發的實現

SpringMVC完成初始化流程以後,就進入Servlet標準生命週期的第二個階段,即「service」階段。在「service」階段中,每一次Http請求到來,容器都會啓動一個請求線程,經過service()方法,委派到doGet()或者doPost()這些方法,完成Http請求的處理。 java

在初始化流程中,SpringMVC巧妙的運用依賴注入讀取參數,並最終創建一個與容器上下文相關聯的Spring子上下文。這個子上下文,就像Struts2中xwork容器同樣,爲接下來的Http處理流程中各類編程元素提供了容身之所。若是說將Spring上下文關聯到Servlet容器中,是SpringMVC框架的第一個亮點,那麼在請求轉發流程中,SpringMVC對各類處理環節編程元素的抽象,就是另一個獨具匠心的亮點。 web

Struts2採起的是一種徹底和Web容器隔離和解耦的事件機制。諸如Action對象、Result對象、Interceptor對象,這些都是徹底脫離Servlet容器的編程元素。Struts2將數據流和事件處理徹底剝離開來,從Http請求中讀取數據後,下面的事件處理流程就只依賴於這些數據,而徹底不知道有Web環境的存在。 spring

反觀SpringMVC,不管HandlerMapping對象、HandlerAdapter對象仍是View對象,這些核心的接口所定義的方法中,HttpServletRequest和HttpServletResponse對象都是直接做爲方法的參數出現的。這也就意味着,框架的設計者,直接將SpringMVC框架和容器綁定到了一塊兒。或者說,整個SpringMVC框架,都是依託着Servlet容器元素來設計的。下面就來看一下,源碼中是如何體現這一點的。 編程

1.請求轉發的入口 設計模式

就像任何一個註冊在容器中的Servlet同樣,DispatcherServlet也是經過本身的service()方法來接收和轉發Http請求到具體的doGet()或doPost()這些方法的。以一次典型的GET請求爲例,通過HttpServlet基類中service()方法的委派,請求會被轉發到doGet()方法中。doGet()方法,在DispatcherServlet的父類FrameworkServlet類中被覆寫。 安全

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

	processRequest(request, response);
}

能夠看到,這裏只是簡單的轉發到processRequest()這個方法。 服務器

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
		throws ServletException, IOException {

	long startTime = System.currentTimeMillis();
	Throwable failureCause = null;

	// Expose current LocaleResolver and request as LocaleContext.
	LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
	LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);

	// Expose current RequestAttributes to current thread.
	RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
	ServletRequestAttributes requestAttributes = null;
	if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
		requestAttributes = new ServletRequestAttributes(request);
		RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
	}

	if (logger.isTraceEnabled()) {
		logger.trace("Bound request context to thread: " + request);
	}

	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 {
		// Clear request attributes and reset thread-bound context.
		LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
		if (requestAttributes != null) {
			RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
			requestAttributes.requestCompleted();
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Cleared thread-bound request context: " + request);
		}

		if (logger.isDebugEnabled()) {
			if (failureCause != null) {
				this.logger.debug("Could not complete request", failureCause);
			}
			else {
				this.logger.debug("Successfully completed request");
			}
		}
		if (this.publishEvents) {
			// Whether or not we succeeded, publish an event.
			long processingTime = System.currentTimeMillis() - startTime;
			this.webApplicationContext.publishEvent(
					new ServletRequestHandledEvent(this,
							request.getRequestURI(), request.getRemoteAddr(),
							request.getMethod(), getServletConfig().getServletName(),
							WebUtils.getSessionId(request), getUsernameForRequest(request),
							processingTime, failureCause));
		}
	}
}

代碼有點長,理解的要點是以doService()方法爲區隔,前一部分是將當前請求的Locale對象和屬性,分別設置到LocaleContextHolder和RequestContextHolder這兩個抽象類中的ThreadLocal對象中,也就是分別將這兩個東西和請求線程作了綁定。在doService()處理結束後,再恢復回請求前的LocaleContextHolder和RequestContextHolder,也即解除線程綁定。每次請求處理結束後,容器上下文都發布了一個ServletRequestHandledEvent事件,你能夠註冊監聽器來監聽該事件。 app

能夠看到,processRequest()方法只是作了一些線程安全的隔離,真正的請求處理,發生在doService()方法中。點開FrameworkServlet類中的doService()方法。 框架

protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
		throws Exception;

又是一個抽象方法,這也是SpringMVC類設計中的慣用伎倆:父類抽象處理流程,子類給予具體的實現。真正的實現是在DispatcherServlet類中。 ide

讓咱們接着看DispatcherServlet類中實現的doService()方法。

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
	if (logger.isDebugEnabled()) {
		String requestUri = urlPathHelper.getRequestUri(request);
		logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +
				" request for [" + requestUri + "]");
	}

	// 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)) {
		logger.debug("Taking snapshot of request attributes before include");
		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));
			}
		}
	}

	// 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());

	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 {
		// Restore the original attribute snapshot, in case of an include.
		if (attributesSnapshot != null) {
			restoreAttributesAfterInclude(request, attributesSnapshot);
		}
	}
}

幾個requet.setAttribute()方法的調用,將前面在初始化流程中實例化的對象設置到http請求的屬性中,供下一步處理使用,其中有容器的上下文對象、本地化解析器等SpringMVC特有的編程元素。不一樣於Struts2中的ValueStack,SpringMVC的數據並無從HttpServletRequest對象中抽離出來再存進另一個編程元素,這也跟SpringMVC的設計思想有關。由於從一開始,SpringMVC的設計者就認爲,不該該將請求處理過程和Web容器徹底隔離

因此,你能夠看到,真正發生請求轉發的方法doDispatch()中,它的參數是HttpServletRequest和HttpServletResponse對象。這給咱們傳遞的意思也很明確,從request中能獲取到一切請求的數據,從response中,咱們又能夠往服務器端輸出任何響應,Http請求的處理,就應該圍繞這兩個對象來設計。咱們不妨能夠將SpringMVC這種設計方案,是從Struts2的過分設計中吸收教訓,而向Servlet編程的一種迴歸和簡化。

2.請求轉發的抽象描述

接下來讓咱們看看doDispatch()這個整個請求轉發流程中最核心的方法。DispatcherServlet所接收的Http請求,通過層層轉發,最終都是彙總到這個方法中來進行最後的請求分發和處理。doDispatch()這個方法的內容,就是SpringMVC整個框架的精華所在。它經過高度抽象的接口,描述出了一個MVC(Model-View-Controller)設計模式的實現方案。Model、View、Controller三種層次的編程元素,在SpringMVC中都有大量的實現類,各類處理細節也是千差萬別。可是,它們最後都是由,也都能由doDispatch()方法來統一描述,這就是接口和抽象的威力,萬變不離其宗。

先來看一下doDispatch()方法的廬山真面目。

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	int interceptorIndex = -1;

	try {
		ModelAndView mv;
		boolean errorView = false;

		try {
			processedRequest = checkMultipart(request);

			// Determine handler for the current request.
			mappedHandler = getHandler(processedRequest, false);
			if (mappedHandler == null || mappedHandler.getHandler() == 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 (logger.isDebugEnabled()) {
					String requestUri = urlPathHelper.getRequestUri(request);
					logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
				}
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}

			// Apply preHandle methods of registered interceptors.
			HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
			if (interceptors != null) {
				for (int i = 0; i < interceptors.length; i++) {
					HandlerInterceptor interceptor = interceptors[i];
					if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
						triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
						return;
					}
					interceptorIndex = i;
				}
			}

			// Actually invoke the handler.
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

			// Do we need view name translation?
			if (mv != null && !mv.hasView()) {
				mv.setViewName(getDefaultViewName(request));
			}

			// Apply postHandle methods of registered interceptors.
			if (interceptors != null) {
				for (int i = interceptors.length - 1; i >= 0; i--) {
					HandlerInterceptor interceptor = interceptors[i];
					interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
				}
			}
		}
		catch (ModelAndViewDefiningException ex) {
			logger.debug("ModelAndViewDefiningException encountered", ex);
			mv = ex.getModelAndView();
		}
		catch (Exception ex) {
			Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
			mv = processHandlerException(processedRequest, response, handler, ex);
			errorView = (mv != null);
		}

		// Did the handler return a view to render?
		if (mv != null && !mv.wasCleared()) {
			render(mv, processedRequest, 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");
			}
		}

		// Trigger after-completion for successful outcome.
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
	}

	catch (Exception ex) {
		// Trigger after-completion for thrown exception.
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
		throw ex;
	}
	catch (Error err) {
		ServletException ex = new NestedServletException("Handler processing failed", err);
		// Trigger after-completion for thrown exception.
		triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
		throw ex;
	}

	finally {
		// Clean up any resources used by a multipart request.
		if (processedRequest != request) {
			cleanupMultipart(processedRequest);
		}
	}
}

真是千呼萬喚始出來,猶抱琵琶半遮面。咱們在第一篇《SpringMVC源碼剖析(一)- 從抽象和接口提及》中所描述的各類編程元素,依次出如今該方法中。HandlerMapping、HandlerAdapter、View這些接口的設計,咱們在第一篇中已經講過。如今咱們來重點關注一下HandlerExecutionChain這個對象。

從上面的代碼中,很明顯能夠看出一條線索,整個方法是圍繞着如何獲取HandlerExecutionChain對象,執行HandlerExecutionChain對象獲得相應的視圖對象,再對視圖進行渲染這條主線來展開的。HandlerExecutionChain對象顯得異常重要。

由於Http請求要進入SpringMVC的處理體系,必須由HandlerMapping接口的實現類映射Http請求,獲得一個封裝後的HandlerExecutionChain對象。再由HandlerAdapter接口的實現類來處理這個HandlerExecutionChain對象所包裝的處理對象,來獲得最後渲染的視圖對象。

視圖對象是用ModelAndView對象來描述的,名字已經很是直白,就是數據和視圖,其中的數據,由HttpServletRequest的屬性獲得,視圖就是由HandlerExecutionChain封裝的處理對象處理後獲得。固然HandlerExecutionChain中的攔截器列表HandlerInterceptor,會在處理過程的先後依次被調用,爲處理過程留下充足的擴展點。

全部的SpringMVC框架元素,都是圍繞着HandlerExecutionChain這個執行鏈來發揮效用。咱們來看看,HandlerExecutionChain類的代碼。

package org.springframework.web.servlet;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.util.CollectionUtils;

public class HandlerExecutionChain {

	private final Object handler;

	private HandlerInterceptor[] interceptors;

	private List<HandlerInterceptor> interceptorList;

	public HandlerExecutionChain(Object handler) {
		this(handler, null);
	}

	public HandlerExecutionChain(Object handler, HandlerInterceptor[] interceptors) {
		if (handler instanceof HandlerExecutionChain) {
			HandlerExecutionChain originalChain = (HandlerExecutionChain) handler;
			this.handler = originalChain.getHandler();
			this.interceptorList = new ArrayList<HandlerInterceptor>();
			CollectionUtils.mergeArrayIntoCollection(originalChain.getInterceptors(), this.interceptorList);
			CollectionUtils.mergeArrayIntoCollection(interceptors, this.interceptorList);
		}
		else {
			this.handler = handler;
			this.interceptors = interceptors;
		}
	}

	public Object getHandler() {
		return this.handler;
	}

	public void addInterceptor(HandlerInterceptor interceptor) {
		initInterceptorList();
		this.interceptorList.add(interceptor);
	}

	public void addInterceptors(HandlerInterceptor[] interceptors) {
		if (interceptors != null) {
			initInterceptorList();
			this.interceptorList.addAll(Arrays.asList(interceptors));
		}
	}

	private void initInterceptorList() {
		if (this.interceptorList == null) {
			this.interceptorList = new ArrayList<HandlerInterceptor>();
		}
		if (this.interceptors != null) {
			this.interceptorList.addAll(Arrays.asList(this.interceptors));
			this.interceptors = null;
		}
	}

	public HandlerInterceptor[] getInterceptors() {
		if (this.interceptors == null && this.interceptorList != null) {
			this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[this.interceptorList.size()]);
		}
		return this.interceptors;
	}

	@Override
	public String toString() {
		if (this.handler == null) {
			return "HandlerExecutionChain with no handler";
		}
		StringBuilder sb = new StringBuilder();
		sb.append("HandlerExecutionChain with handler [").append(this.handler).append("]");
		if (!CollectionUtils.isEmpty(this.interceptorList)) {
			sb.append(" and ").append(this.interceptorList.size()).append(" interceptor");
			if (this.interceptorList.size() > 1) {
				sb.append("s");
			}
		}
		return sb.toString();
	}

}

一個攔截器列表,一個執行對象,這個類的內容十分的簡單,它蘊含的設計思想,卻十分的豐富。

1.攔截器組成的列表,在執行對象被調用的先後,會依次執行。這裏能夠當作是一個的AOP環繞通知,攔截器能夠對處理對象爲所欲爲的進行處理和加強。這裏明顯是吸取了Struts2中攔截器的設計思想。這種AOP環繞式的擴展點設計,也幾乎成爲全部框架必備的內容。

2.實際的處理對象,即handler對象,是由Object對象來引用的。

private final Object handler;

之因此要用一個java世界最基礎的Object對象引用來引用這個handler對象,是由於連特定的接口也不但願綁定在這個handler對象上,從而使handler對象具備最大程度的選擇性和靈活性。

咱們常說,一個框架最高層次的抽象是接口,可是這裏SpringMVC更進了一步。在最後的處理對象上面,SpringMVC沒有對它作任何的限制,只要是java世界中的對象,均可以用來做爲最後的處理對象,來生成視圖。極端一點來講,你甚至能夠將另一個MVC框架集成到SpringMVC中來,也就是爲何SpringMVC官方文檔中,竟然還有集成其餘表現層框架的內容。這一點,在全部表現層框架中,是獨領風騷,冠絕羣雄的。

3.結語

SpringMVC的成功,源於它對開閉原則的運用和遵照。也正所以,才使得整個框架具備如此強大的描述和擴展能力。這也許和SpringMVC出現和興起的時間有關,正是經歷了Struts1到Struts2這些Web開發領域MVC框架的更新換代,它的設計者才能站在前人的肩膀上。知道了如何將事情作的糟糕以後,你或許才知道如何將事情作得好。

但願在這個系列裏面分享的SpringMVC源碼閱讀經驗,能幫助讀者們從更高的層次來審視SpringMVC框架的設計,也但願這裏所描述的一些基本設計思想,能在你更深刻的瞭解SpringMVC的細節時,對你有幫助。哲學纔是惟一的、最終的武器,在一個框架的設計上,尤爲是如此。常常地體會一個框架設計者的設計思想,對你更好的使用它,是有莫大的益處的。

相關文章
相關標籤/搜索