太长不看版
- HTTPServlet 的 Service 方法将请求按类进行分解doXXX
- FrameworkServlet 重写 doXXX 方法,统一调用 doService 方法doXXX 方法统一调用 processRequest 方法doOptions 和 doTrace 有额外的处理其他是直接调用processRequest 主要是初始化 ThreadLocal ,调用 doService 方法,并进行日志等处理ThreadLocal 是 LocalContext 和 AttributesdoService 方法执行核心逻辑,是抽象方法完成后会清空 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}
doOptions 和 doTrace 方法进行了一些额外处理:
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 方法的逻辑为:
- 查找是否有合适的 Handler
- 查找 Handler 是否有支持的 Adapter
- 执行拦截器
- 执行处理
- 解析结果并返回
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 中的查找逻辑为:
- 查找 mappingRegistry 中是否缓存有该 URL 的结果缓存。如果有,那就将缓存直接加入 matches 中
- 如果没有直接匹配,或者直接匹配的缓存结果为空,那么就将将 所有 注册的映射加入 matches 中,并 遍历 检查是否匹配
- 如果 matches 为空,直接返回 handleNoMatch
- 否则,检查最佳匹配是否唯一,如果唯一,则返回最佳匹配的 Handler, 否则返回模糊匹配错误
由于在 RESTful API 中,会有大量相似的 URL,第二步中的遍历将不得不使用正则表达式匹配,而正则表达式匹配的时间复杂度是很高的。因此当 RESTful API 不断增长的时候,性能也会不断变差。
解决方案就是 SpringMVC RESTful 性能优化 中提到的继承 AbstractHandlerMethodMapping 类并重写其匹配逻辑,然后替换掉 SpringMVC 的默认组件。
来源:https://www.cnblogs.com/CounterX/p/16648094.html