1. 程式人生 > >【SpringMVC框架】前端控制器原始碼分析

【SpringMVC框架】前端控制器原始碼分析

前端控制器原始碼分析

雖然前面講了一些springmvc的入門程式和配置檔案中對映器和介面卡的配置,但是我們作為程式設計人員,瞭解框架的部分原始碼還是有必要的,比如前端控制器,它是如何通過Servlet的web.xml配置檔案實現攔截並跳轉至DispatcherServlet的呢?下面我們詳細探討

眾多周知我們的入門程式的web.xml是這麼配置的
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" 
	xmlns="http://java.sun.com/xml/ns/j2ee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
	<!-- SpringMvc前端控制器 -->
	<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<!-- contextConfigLocation配置springmvc載入的配置檔案(配置處理器對映器,介面卡等) 
		如果不配置contextConfigLoaction,預設載入的是/WEB-INF/servlet名稱-servlet.xml(springmvc-servlet.xml)
		-->
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
	</servlet>
	
	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<!-- 第一種:*.action。訪問以.action結尾由DispatcherServlet進行解析;
		 第二種:/,所有訪問的地址都由DispatcherServlet進行解析,對於靜態檔案的解析,
		 我們要配置不讓DispatcherServlet進行解析。使用此種方法可以實現RESTful風格的url;
		 第三種:/*,這樣配置不對,使用這種配置,最終要轉發到一個jsp頁面時,仍然會由
		 DispatcherServlet進行解析jsp地址,它不能根據jsp頁面找到Handler,會報錯-->
		<url-pattern>*.action</url-pattern>
	</servlet-mapping>
	
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

還記不記得springmvc的執行過程:

圖-1.c.springmvc框架



通過前端控制器原始碼分析springmvc的執行過程。

我們點開org.springframework.web.servlet.DispatcherServlet看看,裡面有一個doDiapatch的方法:
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.
				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;
					}
				}


				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}


				try {
					// Actually invoke the handler.
					mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
				}
				finally {
					if (asyncManager.isConcurrentHandlingStarted()) {
						return;
					}
				}


				applyDefaultViewName(request, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Error err) {
			triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				return;
			}
			// Clean up any resources used by a multipart request.
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}

我們來分析這個方法
第一步:前端控制器接收請求

.action型別的URL通過過濾器進入DispatcherServlet類,呼叫其doDiapatch()方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;


		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
                ......
}


第二步:前端控制器呼叫處理器對映器查詢 Handler
在doDiapatch()方法中呼叫了DispatcherServlet類的getHandler方法
HandlerExecutionChain mappedHandler = null;
......
mappedHandler = getHandler(processedRequest, false);
其中getHandler方法:
@Deprecated
	protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
		return getHandler(request);
	}


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;
	}

說明對映器根據request當中的URL,找到了Handler,最終返回一個執行器的鏈(HandlerExecutionChain)。這個鏈裡面有Handler。

第三步:呼叫處理器介面卡執行Handler,得到執行結果ModelAndView
ModelAndView mv = null;
......
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

第四步:檢視渲染,將model資料填充到request域。

檢視解析,得到view:
在doDiapatch()方法中有這一句
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

其中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);
			}
		}


		// Did the handler return a view to render?
		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;
		}


		if (mappedHandler != null) {
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

其中render(mv, request, response);方法中有
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);

渲染方法:
view.render(mv.getModelInternal(), request, response);

呼叫view的渲染方法,將model資料填充到request域
protected void exposeModelAsRequestAttributes(Map<String, Object> model, HttpServletRequest request) throws Exception {
		//遍歷model裡面的資料,填充到request域
		for (Map.Entry<String, Object> entry : model.entrySet()) {
			String modelName = entry.getKey();
			Object modelValue = entry.getValue();
			if (modelValue != null) {
				request.setAttribute(modelName, modelValue);
				if (logger.isDebugEnabled()) {
					logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
							"] to request in view with name '" + getBeanName() + "'");
				}
			}
			else {
				request.removeAttribute(modelName);
				if (logger.isDebugEnabled()) {
					logger.debug("Removed model object '" + modelName +
							"' from request in view with name '" + getBeanName() + "'");
				}
			}
		}
	}

大致瞭解了原始碼,便於更好的理解框架。