1. 程式人生 > 實用技巧 >SpringMVC框架中DispatcherServlet分發請求給對應處理器的流程

SpringMVC框架中DispatcherServlet分發請求給對應處理器的流程

寫在前面

前面已經講解了 詳細解讀 DispatcherServlet 初始化過程(帶時序圖),瞭解到 DispatcherServletinitStrategies(ApplicationContext context) 方法中初始化 HandlerMapping(處理器對映),HandlerAdapter(處理器介面卡),HandlerExceptionResolver(異常解析器),ViewResolver(檢視解析器)等等。

也掌握了 URL 對映是如何註冊到 HandlerMapping 實現中的。

當然,我們常用的 @RequestMapping 比較複雜,所以再往後放放。先掌握攔截器和異常處理器的使用。

攔截器和異常處理器,都是在客戶端(比如 Chrome 瀏覽器)請求時發揮作用的,所以在學習他們之前,我們先來了解一下 SpringMVC 框架中,DispatcherServlet 分發請求到對應處理器的流程。

從 Servlet 規範說起

當 Servlet 容器允許某個 servlet 物件響應請求時,就會呼叫 Servletvoid service(ServletRequest request, ServletResponse response)

方法。

對於選擇哪個 servlet 來響應請求的規則,百度 “servlet-mapping url-pattern”,一抓一大把。連結一篇排名靠前的 servlet的url-pattern匹配規則,介紹了四種匹配規則:

  • 精準匹配(/hello, /admin/home
  • 路徑匹配(/hi/*, /hi/kendoizyu/*, /*
  • 副檔名匹配(*.action, *.jsp, *.htm
  • 預設匹配(/

下面這張圖是 Servlet 容器呼叫 service 方法執行到 doDispatch 的時序圖:

  • HttpServlet # service(HttpServletRequest req, HttpServletResponse resp)

    主要負責區分 HTTP METHOD,然後分發給 doGet,doPost 等等方法。

  • FrameworkServet # processRequest 負責“請求上下文”的儲存和清理。這部分涉及到 ThreadLocal 相關的知識,更多分析可參考 SpringMVC之RequestContextHolder分析

  • FrameworkServet # doService 為 HttpServletRequest 暴露 DispatcherServlet 框架相關的屬性。

  • FrameworkServet # doDispatch 為 HttpServletRequest 獲取對應的處理器,介面卡,執行處理方法,並且包含攔截邏輯。

doDispatch 中跟處理器相關的方法分別是 getHandlergetHandlerAdapterHandlerAdapter # handle

doDispatchHandlerAdapter # handle 前後分別有攔截器相關的方法 HandlerExecutionChain # applyPreHandleapplyPostHandletriggerAfterCompletion

本文重點關注和處理器相關的方法。

getHandler

為當前請求決定一個處理器。

DispatcherServlet # getHandler

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	if (this.handlerMappings != null) {
                // 遍歷 DispatcherServlet 中的 HandlerMapping 例項物件列表
		for (HandlerMapping mapping : this.handlerMappings) {
                        // 具體的 HandlerMapping 例項物件呼叫 getHandler 方法
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
	}
	return null;
}

問題1:成員變數 handlerMappings 是怎麼來的呢?

DispatcherServlet # initHandlerMappings -> 點選展開檢視原始碼

private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;
	if (this.detectAllHandlerMappings) {
                // 找出 DispatcherServlet 和 ContextLoaderListener 上下文中所有實現 HandlerMapping 介面的 Bean
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		Map matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			AnnotationAwareOrderComparator.sort(this.handlerMappings);
		}
	}
	else {
                // 找出上下文中,名稱為 handlerMapping 且實現 HandlerMapping 的 Bean
		try {
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}
	// Ensure we have at least one HandlerMapping, by registering
	// a default HandlerMapping if no other mappings are found.
	if (this.handlerMappings == null) {
                // Spring 框架預設的 HandlerMapping
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isTraceEnabled()) {
			logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
					"': using default strategies from DispatcherServlet.properties");
		}
	}
}

閱讀原始碼之後,我們發現初始化 handlerMappings 的方法中,分為自定義初始化多個或者單個 HandlerMapping Bean,如果沒有自定義就按照框架預設的來:

  1. 預設檢測所有 HandlerMapping Bean:所有實現 HandlerMapping 介面的 Bean

  2. 設定檢測唯一 HandlerMapping Bean:名稱為 handlerMapping 且實現 HandlerMapping 介面的 Bean。需要對 web.xml 的 <servlet> 做一點修改:

<init-param>
      <param-name>detectAllHandlerMappings</param-name>
      <param-value>false</param-value>
</init-param>
  1. 讀取框架預設檔案 DipatcherServlet.properties,獲取 org.springframework.web.servlet.HandlerMapping 對應的 value。(開發者無法修改)
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping

AbstractHandlerMapping # getHandler

這個基類方法是通用的,學習掌握這個方法,基本上就知道 SpringMVC 獲取處理器的方法。

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
	Object handler = getHandlerInternal(request);
	if (handler == null) {
		handler = getDefaultHandler();
	}
	if (handler == null) {
		return null;
	}
	// Bean name or resolved handler?
	if (handler instanceof String) {
		String handlerName = (String) handler;
		handler = obtainApplicationContext().getBean(handlerName);
	}
	HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
	if (logger.isTraceEnabled()) {
		logger.trace("Mapped to " + handler);
	}
	else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
		logger.debug("Mapped to " + executionChain.getHandler());
	}
	if (CorsUtils.isCorsRequest(request)) {
		CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
		CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
		CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
		executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
	}
	return executionChain;
}

getHandlerInternal 方法實現目前有 2 種,一種在子類 AbstractUrlHandlerMapping 中,另一種在子類 AbstractHandlerMethodMapping 中。

getHandlerInternal

URL 與 類級別的處理器 對映

AbstractUrlHandlerMapping # getHandlerInternal -> 點選展開檢視原始碼

@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
	String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
	Object handler = lookupHandler(lookupPath, request);
	if (handler == null) {
		// We need to care for the default handler directly, since we need to
		// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
		Object rawHandler = null;
		if ("/".equals(lookupPath)) {
			rawHandler = getRootHandler();
		}
		if (rawHandler == null) {
			rawHandler = getDefaultHandler();
		}
		if (rawHandler != null) {
			// Bean name or resolved handler?
			if (rawHandler instanceof String) {
				String handlerName = (String) rawHandler;
				rawHandler = obtainApplicationContext().getBean(handlerName);
			}
			validateHandler(rawHandler, request);
			handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
		}
	}
	return handler;
}

執行時序圖如下:

URL 與 方法級別的處理器 對映

AbstractHandlerMethodMapping # getHandlerInternal -> 點選展開檢視原始碼

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List matches = new ArrayList<>();
	List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	if (matches.isEmpty()) {
		// No choice but to go through all mappings...
		addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
	}
	if (!matches.isEmpty()) {
		Comparator comparator = new MatchComparator(getMappingComparator(request));
		matches.sort(comparator);
		Match bestMatch = matches.get(0);
		if (matches.size() > 1) {
			if (logger.isTraceEnabled()) {
				logger.trace(matches.size() + " matching mappings: " + matches);
			}
			if (CorsUtils.isPreFlightRequest(request)) {
				return PREFLIGHT_AMBIGUOUS_MATCH;
			}
			Match secondBestMatch = matches.get(1);
			if (comparator.compare(bestMatch, secondBestMatch) == 0) {
				Method m1 = bestMatch.handlerMethod.getMethod();
				Method m2 = secondBestMatch.handlerMethod.getMethod();
				String uri = request.getRequestURI();
				throw new IllegalStateException(
						"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
			}
		}
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
		handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.handlerMethod;
	}
	else {
		return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
	}
}
private void addMatchingMappings(Collection mappings, List matches, HttpServletRequest request) {
	for (T mapping : mappings) {
		T match = getMatchingMapping(mapping, request);
		if (match != null) {
			matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
		}
	}
}

執行時序圖如下:

這兩個處理器要詳細講解還得結合例項,這裡就不多說了

getHandlerAdapter

通過 getHandler 方法,我們已經得到了“處理器”物件,但是“處理器”物件沒有統一的介面,所以使用介面卡模式進行統一適配。

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

這裡用到了 介面卡模式,示意圖如下:

除了 HandlerMethod 以外,其他的幾個都是方法的直接轉發。右圖 HandlerAdapter 虛線框內的類,這是 supports(Object handler) 的目標物件,拿其中一個舉例:

@Override
public boolean supports(Object handler) {
	return (handler instanceof HttpRequestHandler);
}

所謂的直接轉發,看原始碼:

@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
		throws Exception {
        // 做了一步強轉,就把剩下的交給介面方法處理了!
	((HttpRequestHandler) handler).handleRequest(request, response);
	return null;
}

doDispatch 中執行的“處理器”方法的正是這個 HandlerAdapter # handle(HttpServletRequest request, HttpServletResponse response, Object handler)

總結

doDispatch 的主要流程就是 獲取處理器 getHandler, 獲取處理器介面卡 getHandlerAdapter,執行處理器介面卡的 handle 方法

其中,getHandlerInternal 因子類的不同,而有二類不同的行為,一類是 AbstractUrlHandlerMapping,另一類是AbstractHandlerMethodMapping