SpringMVC框架中DispatcherServlet分发请求给对应处理器

太长不看版

  • HTTPServlet 的 Service 方法将请求按类进行分解
    • 主要是根据HTTP方法的类型调用 doXXX 方法
    • GET 和 HEAD 方法需要对 if-modified-since 进行特殊处理,其他是直接调用
  • FrameworkServlet 重写 doXXX 方法,统一调用 doService 方法
    • doXXX 方法统一调用 processRequest 方法
      • doOptions 和 doTrace 有额外的处理
      • 其他是直接调用
    • processRequest 主要是初始化 ThreadLocal ,调用 doService 方法,并进行日志等处理
      • ThreadLocal 是 LocalContext 和 Attributes
      • doService 方法执行核心逻辑,是抽象方法
      • 完成后会清空 ThreadLocal,打印日志,产生事件。
  • DispatcherServlet 进行具体的实现
    • 重写 doService 方法
      1. 添加 DispatcherServlet 特有的请求属性
      2. 对 HTML 的 include 请求进行处理
      3. 对重定向的请求进行处理
      4. 将请求转交给 doDispatch 方法进行实际的分发
    • doDispatch 方法的逻辑为:
      1. 查找是否有合适的 Handler,该过程在基于RESTful API 设计的 SpringMVC 中有性能问题
      2. 查找 Handler 是否有支持的 Adapter
      3. 执行拦截器
      4. 执行处理
      5. 解析结果并返回

DispatcherServlet 的父类做了什么

DistpathcerServlet 的类图如下,可见其父类为 FrameworkServlet ,同时是一个 HttpServlet .

1.1 HttpServlet 的分发逻辑:

service 方法作为入口, 其逻辑如下:

protected void service(HttpServletRequest req, HttpServletResponse resp)
	throws ServletException, IOException {

	String method = req.getMethod();

	if (method.equals(METHOD_GET)) {
		long lastModified = getLastModified(req);
		if (lastModified == -1) {
			// servlet doesn't support if-modified-since, no reason
			// to go through further expensive logic
			doGet(req, resp);
		} else {
			long ifModifiedSince;
			try {
				ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
			} catch (IllegalArgumentException iae) {
				// Invalid date header - proceed as if none was set
				ifModifiedSince = -1;
			}
			if (ifModifiedSince < (lastModified / 1000 * 1000)) {
				// If the servlet mod time is later, call doGet()
				// Round down to the nearest second for a proper compare
				// A ifModifiedSince of -1 will always be less
				maybeSetLastModified(resp, lastModified);
				doGet(req, resp);
			} else {
				resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
			}
		}

	} else if (method.equals(METHOD_HEAD)) {
		long lastModified = getLastModified(req);
		maybeSetLastModified(resp, lastModified);
		doHead(req, resp);

	} else if (method.equals(METHOD_POST)) {
		doPost(req, resp);

	} else if (method.equals(METHOD_PUT)) {
		doPut(req, resp);

	} else if (method.equals(METHOD_DELETE)) {
		doDelete(req, resp);

	} else if (method.equals(METHOD_OPTIONS)) {
		doOptions(req,resp);

	} else if (method.equals(METHOD_TRACE)) {
		doTrace(req,resp);

	} else {
		//
		// Note that this means NO servlet supports whatever
		// method was requested, anywhere on this server.
		//

		String errMsg = lStrings.getString("http.method_not_implemented");
		Object[] errArgs = new Object[1];
		errArgs[0] = method;
		errMsg = MessageFormat.format(errMsg, errArgs);

		resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
	}
}

可见对于 POST PUT DELETE OPTIONS TRACE 方法都是直接调用对应的具体方法 doXXX, 而 GET 方法会增加 if-modified-since 检查,符合条件后才进行 doGet, 这是为了支持 HTTP 协议的 if-modified-since 请求头。而 HEAD 方法的额外处理也是检查是否需要设置 last-modified 属性。

比如:可以参考这个链接病历包含诊断证明吗?病历是诊断证明吗? - 我要医院证明网

HTTP 协议的 if-modified-since 请求是条件请求,要求返回的资源在指定日期后发生过修改。如果发生修改,则返回 200 OK 以及对应资源,否则返回 304 Not Modified.

1.2 FrameworkServlet 做了什么

1.2.1 重写 doXXX 方法

FrameworkServlet 没有修改 HttpServlet 的分发逻辑,而是将所有的 doXXX 方法调用了同一方法 processRequest

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

	processRequest(request, response);
}

doOptions 和 doTrace 方法进行了一些额外处理:

1.2.1.1 doOptions

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

	// 如果允许转发,并且是 CORS 请求,那么检查它是否是 pre-flight 预检请求
	if (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {
		processRequest(request, response);
		if (response.containsHeader("Allow")) {
			// Proper OPTIONS response coming from a handler - we're done.
			// 如果返回值是 "Allow",即方法允许,那就直接返回
			return;
		}
	}

	// 否则使用 HttpServlet 默认的方法检查是否允许
	// Use response wrapper in order to always add PATCH to the allowed methods
	super.doOptions(request, new HttpServletResponseWrapper(response) {
		@Override
		public void setHeader(String name, String value) {
			// 如果结果是 "Allow", 那么以指定的格式返回
			if ("Allow".equals(name)) {
				value = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();
			}
			super.setHeader(name, value);
		}
	});
}

doOptions 方法例外在 OPTIONS 方法由多个来源,而 Tomcat 只处理来自 CORS 的预检命令。对于不接受 CORS 的 Servlet 或其他来源的 OPTIONS 请求,就调用默认的方法实现。

1.2.1.2 doTrace

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

	if (this.dispatchTraceRequest) {
		processRequest(request, response);
		if ("message/http".equals(response.getContentType())) {
			// Proper TRACE response coming from a handler - we're done.
			return;
		}
	}
	super.doTrace(request, response);
}

doTrace 方法就简单很多,它只需判断是否已经处理了命令,如果没有,则调用默认的方法。

为什么不直接用 processRequest 重写 service 方法?
除了满足里氏替换原则以外,根据 1.1 的分析,我们也可以看到,service 方法中处理了缓存机制,即 last-modified 属性和 if-modified-since 属性的相关处理,以及 OPTIONS 和 TRACE 方法所需的一些额外处理

1.2.2 processRequest

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

	// 分配变量
	long startTime = System.currentTimeMillis();
	Throwable failureCause = null;

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

	// 初始化上下文,实际上是把后两个参数用 ThreadLocal 缓存起来
	initContextHolders(request, localeContext, requestAttributes);

	// 执行真正的业务逻辑 doService
	try {
		doService(request, response);
	}
	catch (ServletException | IOException ex) {
		failureCause = ex;
		throw ex;
	}
	catch (Throwable ex) {
		failureCause = ex;
		throw new NestedServletException("Request processing failed", ex);
	}

	finally {
		// 由于 ThreadLocal 是线程专用的,在线程池场景下需要清空,否则会影响下一次使用。
		resetContextHolders(request, previousLocaleContext, previousAttributes);
		if (requestAttributes != null) {
			requestAttributes.requestCompleted();
		}
		// 输出日志
		logResult(request, response, failureCause, asyncManager);
		// 输出事件
		publishRequestHandledEvent(request, response, startTime, failureCause);
	}
}

processRequest 主要是进行封装和异常处理,并调用 doService 方法进行核心实现。而 doService 是一个抽象方法, DispatcherServlet 就实现了这一方法。

2 DispatcherServlet 的分发流程

2.1 doService 方法

doService 方法的文档注释为:暴露 DispatcherServlet 特有的请求属性,并将请求转交给 doDispatch 进行实际的分发

@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.
	// 如果是 include 请求,保存请求的一个快照,以在 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.
	// 将框架对象暴露给 handler(controller)和 VO
	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 恢复重定向请求的属性
	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);
	}

	// 获取之前的 Path,并将当前 Path 添加到属性中
	RequestPath previousRequestPath = null;
	if (this.parseRequestPath) {
		previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
		ServletRequestPathUtils.parseAndCache(request);
	}

	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);
			}
		}
		if (this.parseRequestPath) {
			// 恢复 RequestPath
			/* 该方法的逻辑为:如果 previousRequestPath 不为空,则将 request 的 PATH_ATTRIBUTE 
			   属性设为 previousRequestPath, 否则删除 PATH_ATTRIBUTE 属性
			 */
			ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
		}
	}
}

该方法的功能是:

  • 添加 DispatcherServlet 特有的请求属性
  • 对 HTML 的 include 请求进行处理
  • 对重定向的请求进行处理
  • 将请求转交给 doDispatch 方法进行实际的分发

HTML 的 include 会引入另一个页面。这里采用了快照技术,将原始属性存放到快照中,并在处理完成后恢复原属性,以避免 include 后对原页面产生影响。

FlashMap 将一个 Request 的属性存储,并存放在 Session 中,这样如果发生了重定向,新产生的 Request 就可以从 flashMapManager 中获取前一个请求的属性(INPUT_FLASH_MAP_ATTRIBUTE)

2.2 doDispatch 方法

@SuppressWarnings("deprecation")
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 {
			// 通过 multipartResolver 判断是否是文件的多部分请求
			processedRequest = checkMultipart(request);
			multipartRequestParsed = (processedRequest != request);

			// Determine handler for the current request.
			// 获取 handler
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null) {
				noHandlerFound(processedRequest, response);
				return;
			}

			// Determine handler adapter for the current request.
			// 获取 HandlerAdapter
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

			// Process last-modified header, if supported by the handler.
			// 进行 last-modified 处理
			String method = request.getMethod();
			boolean isGet = HttpMethod.GET.matches(method);
			if (isGet || HttpMethod.HEAD.matches(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}

			// 检查在 HandlerExecutionChain 中被注册的拦截器,调用 preHandle 方法,返回值为是否放行
			// 内部使用了责任链模式
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}

			// Actually invoke the handler.
			// 最终执行 handler,并获取 ModelAndView
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

			if (asyncManager.isConcurrentHandlingStarted()) {
				return;
			}

			// 如果返回的是 Model(即没有 View ),那么使用默认的 ViewName
			applyDefaultViewName(processedRequest, mv);
			// 执行拦截器的 PostHandle 方法
			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.
			// 从 Spring 4.3 起,handler 发生的 Error 也会被处理
			dispatchException = new NestedServletException("Handler dispatch failed", err);
		}
		// 处理分发的结果,即解析后的 View 对象
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	// 出现异常,则需要继续执行完成拦截器的 afterCompletion 方法
	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.
			// 如果是同步执行,则所有拦截器已经结束,这里把 MultiPart 请求过程中占用的文件全部释放
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}
}

doDispatch 方法的逻辑为:

  1. 查找是否有合适的 Handler
  2. 查找 Handler 是否有支持的 Adapter
  3. 执行拦截器
  4. 执行处理
  5. 解析结果并返回

MultipartResolver 解析请求是否是以允许的方法请求多部分文件,用于文件上传

3 相关问题

3.1 RESTful API 的性能问题

在 doDispatch 方法查找是否有合适的 Handler 的调用链为:

DispatcherServlet::doDispatch -> DispatcherServlet::getHandler -> AbstractHandleMapping::getHandle -> AbstractHandlerMethodMapping::getHandlerInternal -> AbstractHandlerMethodMapping::lookupHandlerMethod

源码为:

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
	List<Match> matches = new ArrayList<>();
	// 查询是否有直接匹配
	List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
	// 如果有直接匹配,那么就将结果缓存
	if (directPathMatches != null) {
		addMatchingMappings(directPathMatches, matches, request);
	}
	// 如果没有直接匹配,或者直接匹配的缓存结果为空,那么就将所有的记录加入
	if (matches.isEmpty()) {
		addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
	}
	if (!matches.isEmpty()) {
		// 如果此时有了匹配,那就查找
		Match bestMatch = matches.get(0);
		// 如果匹配的 match 不止 1 个,那么就检查模糊性
		if (matches.size() > 1) {
			// 如果有超过一个匹配,则排序
			Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
			matches.sort(comparator);
			bestMatch = matches.get(0);
			// 日志 TRACE 级
			if (logger.isTraceEnabled()) {
				logger.trace(matches.size() + " matching mappings: " + matches);
			}
			// 检查模糊性
			if (CorsUtils.isPreFlightRequest(request)) {
				// 如果是 CORS 的预检命令,检查是否有 match 含有 CORS 配置
				for (Match match : matches) {
					if (match.hasCorsConfig()) {
						return PREFLIGHT_AMBIGUOUS_MATCH;
					}
				}
			}
			else {
				// 如果不是 CORS 预检命令,检查次佳匹配是否和最佳匹配同等最佳
				Match secondBestMatch = matches.get(1);
				if (comparator.compare(bestMatch, secondBestMatch) == 0) {
					Method m1 = bestMatch.getHandlerMethod().getMethod();
					Method m2 = secondBestMatch.getHandlerMethod().getMethod();
					String uri = request.getRequestURI();
					throw new IllegalStateException(
						"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
				}
			}
		}
		request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
		handleMatch(bestMatch.mapping, lookupPath, request);
		return bestMatch.getHandlerMethod();
	}
	else {
		// 如果此时 matches 依然为空,那就直接返回
		return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
	}
}

AbstractHandlerMethodMapping::lookupHandlerMethod 中的查找逻辑为:

  1. 查找 mappingRegistry 中是否缓存有该 URL 的结果缓存。如果有,那就将缓存直接加入 matches 中
  2. 如果没有直接匹配,或者直接匹配的缓存结果为空,那么就将将所有注册的映射加入 matches 中,并遍历检查是否匹配
  3. 如果 matches 为空,直接返回 handleNoMatch
  4. 否则,检查最佳匹配是否唯一,如果唯一,则返回最佳匹配的 Handler, 否则返回模糊匹配错误

由于在 RESTful API 中,会有大量相似的 URL,第二步中的遍历将不得不使用正则表达式匹配,而正则表达式匹配的时间复杂度是很高的。因此当 RESTful API 不断增长的时候,性能也会不断变差。

解决方案就是中提到的继承 AbstractHandlerMethodMapping 类并重写其匹配逻辑,然后替换掉 SpringMVC 的默认组件。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值