网络知识 娱乐 DispatcherServlet 分发流程

DispatcherServlet 分发流程

太长不看版

  • HTTPServletService 方法将请求按类进行分解doXXX
  • FrameworkServlet 重写 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)ntthrows ServletException, IOException {nntString method = req.getMethod();nntif (method.equals(METHOD_GET)) {nttlong lastModified = getLastModified(req);nttif (lastModified == -1) {nttt// servlet doesn't support if-modified-since, no reasonnttt// to go through further expensive logicntttdoGet(req, resp);ntt} else {ntttlong ifModifiedSince;nttttry {nttttifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);nttt} catch (IllegalArgumentException iae) {ntttt// Invalid date header - proceed as if none was setnttttifModifiedSince = -1;nttt}ntttif (ifModifiedSince < (lastModified / 1000 * 1000)) {ntttt// If the servlet mod time is later, call doGet()ntttt// Round down to the nearest second for a proper comparentttt// A ifModifiedSince of -1 will always be lessnttttmaybeSetLastModified(resp, lastModified);nttttdoGet(req, resp);nttt} else {nttttresp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);nttt}ntt}nnt} else if (method.equals(METHOD_HEAD)) {nttlong lastModified = getLastModified(req);nttmaybeSetLastModified(resp, lastModified);nttdoHead(req, resp);nnt} else if (method.equals(METHOD_POST)) {nttdoPost(req, resp);nnt} else if (method.equals(METHOD_PUT)) {nttdoPut(req, resp);nnt} else if (method.equals(METHOD_DELETE)) {nttdoDelete(req, resp);nnt} else if (method.equals(METHOD_OPTIONS)) {nttdoOptions(req,resp);nnt} else if (method.equals(METHOD_TRACE)) {nttdoTrace(req,resp);nnt} else {ntt//ntt// Note that this means NO servlet supports whateverntt// method was requested, anywhere on this server.ntt//nnttString errMsg = lStrings.getString("http.method_not_implemented");nttObject[] errArgs = new Object[1];ntterrArgs[0] = method;ntterrMsg = MessageFormat.format(errMsg, errArgs);nnttresp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);nt}n}

可见对于 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

@Overridenprotected final void doGet(HttpServletRequest request, HttpServletResponse response)ntthrows ServletException, IOException {nntprocessRequest(request, response);n}

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

1.2.1.1 doOptions

@Overridenprotected void doOptions(HttpServletRequest request, HttpServletResponse response)ntthrows ServletException, IOException {nnt// 如果允许转发,并且是 CORS 请求,那么检查它是否是 pre-flight 预检请求ntif (this.dispatchOptionsRequest || CorsUtils.isPreFlightRequest(request)) {nttprocessRequest(request, response);nttif (response.containsHeader("Allow")) {nttt// Proper OPTIONS response coming from a handler - we're done.nttt// 如果返回值是 "Allow",即方法允许,那就直接返回ntttreturn;ntt}nt}nnt// 否则使用 HttpServlet 默认的方法检查是否允许nt// Use response wrapper in order to always add PATCH to the allowed methodsntsuper.doOptions(request, new HttpServletResponseWrapper(response) {ntt@Overridenttpublic void setHeader(String name, String value) {nttt// 如果结果是 "Allow", 那么以指定的格式返回ntttif ("Allow".equals(name)) {nttttvalue = (StringUtils.hasLength(value) ? value + ", " : "") + HttpMethod.PATCH.name();nttt}ntttsuper.setHeader(name, value);ntt}nt});n}

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

1.2.1.2 doTrace

@Overridenprotected void doTrace(HttpServletRequest request, HttpServletResponse response)ntthrows ServletException, IOException {nntif (this.dispatchTraceRequest) {nttprocessRequest(request, response);nttif ("message/http".equals(response.getContentType())) {nttt// Proper TRACE response coming from a handler - we're done.ntttreturn;ntt}nt}ntsuper.doTrace(request, response);n}

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)ntthrows ServletException, IOException {nnt// 分配变量ntlong startTime = System.currentTimeMillis();ntThrowable failureCause = null;nntLocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();ntLocaleContext localeContext = buildLocaleContext(request);nntRequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();ntServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);nntWebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);ntasyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());nnt// 初始化上下文,实际上是把后两个参数用 ThreadLocal 缓存起来ntinitContextHolders(request, localeContext, requestAttributes);nnt// 执行真正的业务逻辑 doServicenttry {nttdoService(request, response);nt}ntcatch (ServletException | IOException ex) {nttfailureCause = ex;nttthrow ex;nt}ntcatch (Throwable ex) {nttfailureCause = ex;nttthrow new NestedServletException("Request processing failed", ex);nt}nntfinally {ntt// 由于 ThreadLocal 是线程专用的,在线程池场景下需要清空,否则会影响下一次使用。nttresetContextHolders(request, previousLocaleContext, previousAttributes);nttif (requestAttributes != null) {ntttrequestAttributes.requestCompleted();ntt}ntt// 输出日志nttlogResult(request, response, failureCause, asyncManager);ntt// 输出事件nttpublishRequestHandledEvent(request, response, startTime, failureCause);nt}n}

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

2 DispatcherServlet 的分发流程

2.1 doService 方法

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

@Overridenprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {ntlogRequest(request);nnt// Keep a snapshot of the request attributes in case of an include,nt// to be able to restore the original attributes after the include.nt// 如果是 include 请求,保存请求的一个快照,以在 include 中保存原始属性ntMap<String, Object> attributesSnapshot = null;ntif (WebUtils.isIncludeRequest(request)) {nttattributesSnapshot = new HashMap<>();nttEnumeration<?> attrNames = request.getAttributeNames();nttwhile (attrNames.hasMoreElements()) {ntttString attrName = (String) attrNames.nextElement();ntttif (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {nttttattributesSnapshot.put(attrName, request.getAttribute(attrName));nttt}ntt}nt}nnt// Make framework objects available to handlers and view objects.nt// 将框架对象暴露给 handler(controller)和 VOntrequest.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());ntrequest.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);ntrequest.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);ntrequest.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());nnt// 通过 FlashMap 恢复重定向请求的属性ntif (this.flashMapManager != null) {nttFlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);nttif (inputFlashMap != null) {ntttrequest.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));ntt}nttrequest.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());nttrequest.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);nt}nnt// 获取之前的 Path,并将当前 Path 添加到属性中ntRequestPath previousRequestPath = null;ntif (this.parseRequestPath) {nttpreviousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);nttServletRequestPathUtils.parseAndCache(request);nt}nnttry {nttdoDispatch(request, response);nt}ntfinally {nttif (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {nttt// Restore the original attribute snapshot, in case of an include.nttt// 根据原始快照恢复属性ntttif (attributesSnapshot != null) {nttttrestoreAttributesAfterInclude(request, attributesSnapshot);nttt}ntt}nttif (this.parseRequestPath) {nttt// 恢复 RequestPathnttt/* 该方法的逻辑为:如果 previousRequestPath 不为空,则将 request 的 PATH_ATTRIBUTE nttt 属性设为 previousRequestPath, 否则删除 PATH_ATTRIBUTE 属性nttt */ntttServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);ntt}nt}n}

该方法的功能是:

DispatcherServletndoDispatchn

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

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

2.2 doDispatch 方法

@SuppressWarnings("deprecation")nprotected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {nt// 初始化变量ntHttpServletRequest processedRequest = request;ntHandlerExecutionChain mappedHandler = null;ntboolean multipartRequestParsed = false;nntWebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);nnttry {nttModelAndView mv = null;nttException dispatchException = null;nntttry {nttt// 通过 multipartResolver 判断是否是文件的多部分请求ntttprocessedRequest = checkMultipart(request);ntttmultipartRequestParsed = (processedRequest != request);nnttt// Determine handler for the current request.nttt// 获取 handlerntttmappedHandler = getHandler(processedRequest);ntttif (mappedHandler == null) {nttttnoHandlerFound(processedRequest, response);nttttreturn;nttt}nnttt// Determine handler adapter for the current request.nttt// 获取 HandlerAdapterntttHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());nnttt// Process last-modified header, if supported by the handler.nttt// 进行 last-modified 处理ntttString method = request.getMethod();ntttboolean isGet = HttpMethod.GET.matches(method);ntttif (isGet || HttpMethod.HEAD.matches(method)) {nttttlong lastModified = ha.getLastModified(request, mappedHandler.getHandler());nttttif (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {ntttttreturn;ntttt}nttt}nnttt// 检查在 HandlerExecutionChain 中被注册的拦截器,调用 preHandle 方法,返回值为是否放行nttt// 内部使用了责任链模式ntttif (!mappedHandler.applyPreHandle(processedRequest, response)) {nttttreturn;nttt}nnttt// Actually invoke the handler.nttt// 最终执行 handler,并获取 ModelAndViewntttmv = ha.handle(processedRequest, response, mappedHandler.getHandler());nntttif (asyncManager.isConcurrentHandlingStarted()) {nttttreturn;nttt}nnttt// 如果返回的是 Model(即没有 View ),那么使用默认的 ViewNamentttapplyDefaultViewName(processedRequest, mv);nttt// 执行拦截器的 PostHandle 方法ntttmappedHandler.applyPostHandle(processedRequest, response, mv);ntt}nttcatch (Exception ex) {ntttdispatchException = ex;ntt}nttcatch (Throwable err) {nttt// As of 4.3, we're processing Errors thrown from handler methods as well,nttt// making them available for @ExceptionHandler methods and other scenarios.nttt// 从 Spring 4.3 起,handler 发生的 Error 也会被处理ntttdispatchException = new NestedServletException("Handler dispatch failed", err);ntt}ntt// 处理分发的结果,即解析后的 View 对象nttprocessDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);nt}nt// 出现异常,则需要继续执行完成拦截器的 afterCompletion 方法ntcatch (Exception ex) {ntttriggerAfterCompletion(processedRequest, response, mappedHandler, ex);nt}ntcatch (Throwable err) {ntttriggerAfterCompletion(processedRequest, response, mappedHandler,nttttttt new NestedServletException("Handler processing failed", err));nt}ntfinally {nttif (asyncManager.isConcurrentHandlingStarted()) {nttt// Instead of postHandle and afterCompletionnttt// 如果是并发执行,则异步结束拦截器ntttif (mappedHandler != null) {nttttmappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);nttt}ntt}nttelse {nttt// Clean up any resources used by a multipart request.nttt// 如果是同步执行,则所有拦截器已经结束,这里把 MultiPart 请求过程中占用的文件全部释放ntttif (multipartRequestParsed) {nttttcleanupMultipart(processedRequest);nttt}ntt}nt}n}

doDispatch 方法的逻辑为:

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

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

3 相关问题

3.1 RESTful API 的性能问题

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

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

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

源码为:

@Nullablenprotected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {ntList<Match> matches = new ArrayList<>();nt// 查询是否有直接匹配ntList<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);nt// 如果有直接匹配,那么就将结果缓存ntif (directPathMatches != null) {nttaddMatchingMappings(directPathMatches, matches, request);nt}nt// 如果没有直接匹配,或者直接匹配的缓存结果为空,那么就将所有的记录加入ntif (matches.isEmpty()) {nttaddMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);nt}ntif (!matches.isEmpty()) {ntt// 如果此时有了匹配,那就查找nttMatch bestMatch = matches.get(0);ntt// 如果匹配的 match 不止 1 个,那么就检查模糊性nttif (matches.size() > 1) {nttt// 如果有超过一个匹配,则排序ntttComparator<Match> comparator = new MatchComparator(getMappingComparator(request));ntttmatches.sort(comparator);ntttbestMatch = matches.get(0);nttt// 日志 TRACE 级ntttif (logger.isTraceEnabled()) {nttttlogger.trace(matches.size() + " matching mappings: " + matches);nttt}nttt// 检查模糊性ntttif (CorsUtils.isPreFlightRequest(request)) {ntttt// 如果是 CORS 的预检命令,检查是否有 match 含有 CORS 配置nttttfor (Match match : matches) {ntttttif (match.hasCorsConfig()) {nttttttreturn PREFLIGHT_AMBIGUOUS_MATCH;nttttt}ntttt}nttt}ntttelse {ntttt// 如果不是 CORS 预检命令,检查次佳匹配是否和最佳匹配同等最佳nttttMatch secondBestMatch = matches.get(1);nttttif (comparator.compare(bestMatch, secondBestMatch) == 0) {ntttttMethod m1 = bestMatch.getHandlerMethod().getMethod();ntttttMethod m2 = secondBestMatch.getHandlerMethod().getMethod();ntttttString uri = request.getRequestURI();ntttttthrow new IllegalStateException(ntttttt"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");ntttt}nttt}ntt}nttrequest.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());ntthandleMatch(bestMatch.mapping, lookupPath, request);nttreturn bestMatch.getHandlerMethod();nt}ntelse {ntt// 如果此时 matches 依然为空,那就直接返回nttreturn handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);nt}n}

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

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

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

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

来源:https://www.cnblogs.com/CounterX/p/16648094.html