0 太长不看版
HTTPServlet
的Service
方法将请求按类进行分解- 主要是根据HTTP方法的类型调用
doXXX
方法 - GET 和 HEAD 方法需要对 if-modified-since 进行特殊处理,其他是直接调用
- 主要是根据HTTP方法的类型调用
FrameworkServlet
重写doXXX
方法,统一调用doService
方法doXXX
方法统一调用processRequest
方法doOptions
和doTrace
有额外的处理- 其他是直接调用
processRequest
主要是初始化ThreadLocal
,调用doService
方法,并进行日志等处理ThreadLocal
是LocalContext
和Attributes
doService
方法执行核心逻辑,是抽象方法- 完成后会清空
ThreadLocal
,打印日志,产生事件。
DispatcherServlet
进行具体的实现- 重写
doService
方法- 添加
DispatcherServlet
特有的请求属性 - 对 HTML 的 include 请求进行处理
- 对重定向的请求进行处理
- 将请求转交给
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
。
@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
方法的逻辑为:
- 查找是否有合适的 Handler
- 查找 Handler 是否有支持的 Adapter
- 执行拦截器
- 执行处理
- 解析结果并返回
MultipartResolver 解析请求是否是以允许的方法请求多部分文件,用于文件上传
3 相关问题
3.1 RESTful API 的性能问题
问题来源参见:SpringMVC RESTful 性能优化
在 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
中的查找逻辑为:
- 查找
mappingRegistry
中是否缓存有该 URL 的结果缓存。如果有,那就将缓存直接加入matches
中 - 如果没有直接匹配,或者直接匹配的缓存结果为空,那么就将将所有注册的映射加入
matches
中,并遍历检查是否匹配 - 如果
matches
为空,直接返回handleNoMatch
- 否则,检查最佳匹配是否唯一,如果唯一,则返回最佳匹配的 Handler, 否则返回模糊匹配错误
由于在 RESTful API 中,会有大量相似的 URL,第二步中的遍历将不得不使用正则表达式匹配,而正则表达式匹配的时间复杂度是很高的。因此当 RESTful API 不断增长的时候,性能也会不断变差。
解决方案就是SpringMVC RESTful 性能优化中提到的继承 AbstractHandlerMethodMapping
类并重写其匹配逻辑,然后替换掉 SpringMVC 的默认组件。