龙空技术网

DispatcherServlet 分发流程

陈三岁98 359

前言:

当前小伙伴们对“如何在一个html中引入另一个html”大致比较关注,看官们都想要剖析一些“如何在一个html中引入另一个html”的相关资讯。那么小编在网上收集了一些有关“如何在一个html中引入另一个html””的相关知识,希望大家能喜欢,我们快快来学习一下吧!

太长不看版HTTPServletService 方法将请求按类进行分解doXXXFrameworkServlet 重写 doXXX 方法,统一调用 doService 方法doXXX 方法统一调用 processRequest 方法doOptionsdoTrace 有额外的处理其他是直接调用processRequest 主要是初始化 ThreadLocal ,调用 doService 方法,并进行日志等处理ThreadLocalLocalContextAttributesdoService 方法执行核心逻辑,是抽象方法完成后会清空 ThreadLocal ,打印日志,产生事件。DispatcherServlet 进行具体的实现重写 doService 方法DispatcherServlet doDispatch doDispatch 方法的逻辑为:查找是否有合适的 Handler,该过程在基于RESTful API 设计的 SpringMVC 中有性能问题查找 Handler 是否有支持的 Adapter执行拦截器执行处理解析结果并返回1,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

@Overrideprotected final void doGet(HttpServletRequest request, HttpServletResponse response)	throws ServletException, IOException {	processRequest(request, response);}

doOptionsdoTrace 方法进行了一些额外处理:

1.2.1.1 doOptions

@Overrideprotected 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

@Overrideprotected 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 进行实际的分发

@Overrideprotected 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);		}	}}

该方法的功能是:

DispatcherServletdoDispatch

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 方法的逻辑为:

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

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

3 相关问题3.1 RESTful API 的性能问题

问题来源参见: SpringMVC RESTful 性能优化

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

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

源码为:

@Nullableprotected 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 中的查找逻辑为:

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

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

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

来源:

标签: #如何在一个html中引入另一个html