SpringMVC源码解析(四)——请求处理

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

前言

    这一篇,将着手介绍一次请求的处理。用到了 HandlerMapping、HandlerAdapter 知识,如果遇到不是太了解,可以回顾下。

 

源码分析

    其实 DispatcherServlet 也只是 Servlet 的一个实现,只不过它集成了 SpringMVC 的几个功能组件(例如视图解析器),对请求及响应进行加工处理,所以探索一个 Servlet 实现,先从它的 service 方法实现开始,来看下 javax.servlet.http.HttpServlet 的实现。

public abstract class HttpServlet extends GenericServlet {@Overridepublic void service(ServletRequest req, ServletResponse res)throws ServletException, IOException {HttpServletRequest request;HttpServletResponse response;try {request = (HttpServletRequest) req;response = (HttpServletResponse) res;} catch (ClassCastException e) {throw new ServletException("non-HTTP request or response");}// 向下转型service(request, response);}protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {String method = req.getMethod();// 根据请求方式调用不同的 doXxx方法if (method.equals(METHOD_GET)) {// 固定返回-1,说明缓存机制交由子类扩展long lastModified = getLastModified(req);if (lastModified == -1) {doGet(req, resp);} else {/*** 缓存机制:首次请求后添加 “Last-Modefied” 的响应头,* 第二次请求发送请求头 If-Modified-Since* 如果服务端内容没有变化,则自动返回 304状态码*/long ifModifiedSince;try {ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);} catch (IllegalArgumentException iae) {ifModifiedSince = -1;}if (ifModifiedSince < (lastModified / 1000 * 1000)) {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 {// 对于不支持请求方式,通过响应流输出错误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);}}
}

    可以看到 service 就是做了向下转型,调用了 service(HttpServletRequest req, HttpServletResponse resp)。然后在里面根据请求方式,调用不同的 doXxx 方法。以上属于 servlet-api 自身的实现,接下来看看 SpringMVC 如何在此基础上改造。

// HttpServletBean是 HttpServlet子类
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {// 覆盖 service方法@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());// 支持 Patch请求或没有指定请求方法if (httpMethod == HttpMethod.PATCH || httpMethod == null) {// 不管什么请求,都会调用公共的处理方法processRequest(request, response);} else {super.service(request, response);}}@Overrideprotected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);}@Overrideprotected final void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);}protected final void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 请求开始时间,用于计算请求耗时long startTime = System.currentTimeMillis();Throwable failureCause = null;LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();// 通过"accept-language"请求头构造 LocaleContextLocaleContext localeContext = buildLocaleContext(request);// 获取当前线程请求的 RequestAttributesRequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);// 异步请求管理器WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());// 将 LocaleContext、ServletRequestAttributes与当前线程绑定initContextHolders(request, localeContext, requestAttributes);try {// 该类定义的抽象方法,子类 DispatcherServlet实现doService(request, response);}.....// 省略 catch处理finally{resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}.....// 省略日志publishRequestHandledEvent(request, response, startTime, failureCause);}}// 发布 ServletRequestHandledEvent事件private void publishRequestHandledEvent( HttpServletRequest request, HttpServletResponse response, long startTime, Throwable failureCause) {// 默认为 trueif (this.publishEvents) {// 请求耗时long processingTime = System.currentTimeMillis() - startTime;// 响应码int statusCode = (responseGetStatusAvailable ? response.getStatus() : -1);// 封装了 请求的 url、remoteAddr、请求方式、sessionId、处理时间、响应码等信息this.webApplicationContext.publishEvent(new ServletRequestHandledEvent(this,request.getRequestURI(), request.getRemoteAddr(),request.getMethod(), getServletConfig().getServletName(),WebUtils.getSessionId(request), getUsernameForRequest(request),processingTime, failureCause, statusCode));}}
}

    除了支持 servlet 自身支持的 7 种请求外,另外支持了 PATCH 方式请求。这里只是列举了 doGet、doPost,其实最终都调用了 processRequest 方法。

    processRequest 方法通过调用 doService 来处理请求,在处理结束后发布了 ServletRequestHandledEvent 事件,可以自定义 ApplicationListener 来监听此事件。

 

doService

public class DispatcherServlet extends FrameworkServlet {@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {.....// 省略日志// 如果 attributes含有 “javax.servlet.include.request_uri”,保留属性的快照// 用于支持 <jsp:incluede>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)) {// 将属性压入map中attributesSnapshot.put(attrName, request.getAttribute(attrName));}}}// 设置上下文、解析器等属性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并给赋值给当前请求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);try {// 关注此方法doDispatch(request, response);} finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// 还原属性快照if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}}}
}

    DispatcherServlet 来进行主要的处理实现。doService 进行一些属性的设置之后,调用 doDispatch 方法进行处理

 

doDispatch

    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判断是否需要处理,如果没配置不会处理* 以 CommonsMultipartResolver为例* 检测 contentType是否为 multipart/form-data且必须是 POST请求*/processedRequest = checkMultipart(request);// 如果满足上述条件,会包装成 DefaultMultipartHttpServletRequest// 所以 multipartRequestParsed 为 truemultipartRequestParsed = (processedRequest != request);// 根据 request找到对应的 HandlermappedHandler = getHandler(processedRequest);if (mappedHandler == null) {// 没找到 Handler,通过response响应 404错误信息noHandlerFound(processedRequest, response);return;}// 根据 Handler找到对应的适配器HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 对 last-modified 头支持:只有 Get 和 Head请求支持String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());....// 省略日志// 对于缓存逻辑的判断,见 ServletWebRequest.checkNotModifiedif (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {// 满足条件直接返回return;}}// 调用拦截器的 preHandle,返回true才能通过if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 调用 HandlerAdapter.handle返回处理后的 ModelAndViewmv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}// 如果没有 ModelAndView返回,则根据请求默认给出一个视图名称applyDefaultViewName(processedRequest, mv);// 调用拦截器的 postHandlemappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception ex) {dispatchException = ex;} catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}// 视图处理(包含异常视图),最后会调用拦截器的 triggerAfterCompletionprocessDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);} catch (Exception ex) {// 调用拦截器的 triggerAfterCompletiontriggerAfterCompletion(processedRequest, response, mappedHandler, ex);} catch (Throwable err) {// 调用拦截器的 triggerAfterCompletiontriggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {// 调用 AsyncHandlerInterceptor.afterConcurrentHandlingStarted// 用于在异步请求处理开始之后回调mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else {// 如果是文件上传请求,需要清理资源if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}}

    通过层层的调用,终于来到了 doDispatch ,这个方法大致能够看到请求的处理步骤概览:

  • 首先判断是否为文件上传的请求:和普通的请求不一样,这种请求需要在请求结束后清理文件解析过程中创建的 MultipartFile (可能会在磁盘上保留有临时数据);
  • 获取 Handler :从 HandlerMapping初始化建立的映射关系中找出;
  • 拦截器 HandlerInterceptor.preHandle 调用;
  • HandlerAdapter 适配:从 HandlerAdapter 初始化注册的所有适配器中,找到支持对应 Handler 的适配器,调用 handle 方法处理(见 HandlerAdapter)
  • 拦截器 HandlerInterceptor.postHandle 调用;
  • 调用 processDispatchResult 进行视图处理
  • 拦截器 HandlerInterceptor.afterCompletion 调用(异常同样会触发,并且会作为参数传入)

    接下来我们来看几个主要步骤的解析。

 

getHandler

    @Nullableprotected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {// 遍历所有注册的 HandlerMappingfor (HandlerMapping hm : this.handlerMappings) {....// 省略日志// 找到匹配的HandlerExecutionChain(不为null)HandlerExecutionChain handler = hm.getHandler(request);if (handler != null) {return handler;}}}return null;}

    调用 HandlerMapping.getHandler 获取请求对应的 Handler。来看实现:

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport implements HandlerMapping, Ordered {@Override@Nullablepublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 根据 request获取对应的 Handler(子类实现)Object handler = getHandlerInternal(request);// 如果没有找到对应 Handler,使用默认Handlerif (handler == null) {handler = getDefaultHandler();}// 如果默认的 Handler没有,则返回 nullif (handler == null) {return null;}// Handler为 String类型,说明是beanName,调用 getBean获取if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}// 执行链构造:Handler和拦截器HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);// 判断跨域请求:是否带有“Origin”请求头if (CorsUtils.isCorsRequest(request)) {// 全局跨域配置CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);// 单个 Handler配置(注解 @CrossOrigin其实就是对单个 Handler的配置)CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);// 配置合并CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);// 添加 CorsInterceptor拦截器(使用合并后的配置)executionChain = getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;}// 执行链构造protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);// 遍历 List<HandlerInterceptor>for (HandlerInterceptor interceptor : this.adaptedInterceptors) {if (interceptor instanceof MappedInterceptor) {MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {// 将匹配的 MappedInterceptor加入处理链chain.addInterceptor(mappedInterceptor.getInterceptor());}} else {// 其他的直接加入chain.addInterceptor(interceptor);}}return chain;}// 跨域拦截器加入protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,HandlerExecutionChain chain, CorsConfiguration config) {// 条件:带有“Origin”、“Access-Control-Request-Method”请求头的 options请求if (CorsUtils.isPreFlightRequest(request)) {HandlerInterceptor[] interceptors = chain.getInterceptors();// 使用 PreFlightHandler替代原本的 Handler处理请求chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);} else {// 添加 CorsInterceptor拦截器(拦截器末尾)chain.addInterceptor(new CorsInterceptor(config));}return chain;}
}

    抽象父类 AbstractHandlerMapping 实现了 执行链的构造 以及 “跨域”相关处理(拦截器),查找 Handler 的逻辑交由子类实现(getHandlerInternal)。回想一下 HandlerMapping 注册 Handler 的逻辑分为了两个分支 AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping (见 HandlerMapping 初始化),因此查找逻辑也随注册逻辑不同而不同。

 

AbstractUrlHandlerMapping 分支

public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {@Overrideprotected Object getHandlerInternal(HttpServletRequest request) throws Exception {// 这一步我们会取到截取后的请求相对地址String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);// 根据相对地址找到对应的 HandlerObject handler = lookupHandler(lookupPath, request);if (handler == null) {// 没有的话,依次查询根 Handler、默认 HandlerObject rawHandler = null;if ("/".equals(lookupPath)) {rawHandler = getRootHandler();}if (rawHandler == null) {rawHandler = getDefaultHandler();}if (rawHandler != null) {// 如果 Handler是 beanName,调用 getBean获取if (rawHandler instanceof String) {String handlerName = (String) rawHandler;rawHandler = getApplicationContext().getBean(handlerName);}// 校验:由 DefaultAnnotationHandlerMapping实现// 看请求是否满足 @RequestMapping指定的 method、param、headervalidateHandler(rawHandler, request);// 使用 Handler创建执行链,链头添加 PathExposingHandlerInterceptor拦截器handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);}}....// 省略日志return handler;}protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {// 从映射关系中找出请求对应的 Handler(直接路径)// 映射关系的初始化见 HandlerMapping初始化Object handler = this.handlerMap.get(urlPath);if (handler != null) {// beanName以及 Handler校验,上面讲过了if (handler instanceof String) {String handlerName = (String) handler;handler = getApplicationContext().getBean(handlerName);}validateHandler(handler, request);// 构造执行链return buildPathExposingHandler(handler, urlPath, urlPath, null);}// 直接路径中未找到,使用通配符匹配List<String> matchingPatterns = new ArrayList<String>();for (String registeredPattern : this.handlerMap.keySet()) {if (getPathMatcher().match(registeredPattern, urlPath)) {matchingPatterns.add(registeredPattern);} else if (useTrailingSlashMatch()) {if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {matchingPatterns.add(registeredPattern + "/");}}}String bestMatch = null;// 使用模式匹配后,查找最匹配的 HandlerComparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);if (!matchingPatterns.isEmpty()) {Collections.sort(matchingPatterns, patternComparator);....// 省略日志bestMatch = matchingPatterns.get(0);}if (bestMatch != null) {handler = this.handlerMap.get(bestMatch);if (handler == null) {if (bestMatch.endsWith("/")) {handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));}if (handler == null) {throw new IllegalStateException("Could not find handler for best pattern match [" + bestMatch + "]");}}// beanName以及 Handler校验,上面讲过了if (handler instanceof String) {String handlerName = (String) handler;handler = getApplicationContext().getBean(handlerName);}validateHandler(handler, request);String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);// 可能存在多个“最佳模式”,让我们确保所有这些模式都有正确的URI模板变量Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();for (String matchingPattern : matchingPatterns) {if (patternComparator.compare(bestMatch, matchingPattern) == 0) {Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);uriTemplateVariables.putAll(decodedVars);}}....// 省略日志// 构造执行链return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);}// 没有匹配的 Handler返回 nullreturn null;}
}

    首先调用 UrlPathHelper.getLookupPathForRequest 获取请求的相对路径。以 Tomcat 举例,配置的 <Context path="xxx"> 项目根路径,那么对应的 web 应用所有的请求,都要添加 “xxx” 前缀,但我们的应用对此是无感知的,所以框架层面要把这些截取后,再去查找 Handler。来看下截取逻辑:

public class UrlPathHelper {public String getLookupPathForRequest(HttpServletRequest request) {// 默认为 falseif (this.alwaysUseFullPath) {return getPathWithinApplication(request);}String rest = getPathWithinServletMapping(request);if (!"".equals(rest)) {return rest;} else {return getPathWithinApplication(request);}}public String getPathWithinServletMapping(HttpServletRequest request) {// 获取截取后的相对地址String pathWithinApp = getPathWithinApplication(request);// 获取的 <servlet>指定的 <url-pattern>(移除通配符后)String servletPath = getServletPath(request);// 把 pathWithinApp中的“//”替换成“/”String sanitizedPathWithinApp = getSanitizedPath(pathWithinApp);String path;// 这一步主要就是对请求地址中多余的“/”进行移除匹配if (servletPath.contains(sanitizedPathWithinApp)) {// 同样的,把请求的相对地址中,servletPath截取掉path = getRemainingPath(sanitizedPathWithinApp, servletPath, false);} else {// 同样的,把请求的相对地址中,servletPath截取掉path = getRemainingPath(pathWithinApp, servletPath, false);}if (path != null) {return path;} else {// 如果请求不在 servlet指定的 <url-pattern>下String pathInfo = request.getPathInfo();if (pathInfo != null) {return pathInfo;}if (!this.urlDecode) {path = getRemainingPath(decodeInternal(request, pathWithinApp), servletPath, false);if (path != null) {return pathWithinApp;}}return servletPath;}}public String getPathWithinApplication(HttpServletRequest request) {// 获取的项目根路径String contextPath = getContextPath(request);// 获取请求的相对地址String requestUri = getRequestUri(request);// 把请求的相对地址中的项目根路径截去String path = getRemainingPath(requestUri, contextPath, true);if (path != null) {return (StringUtils.hasText(path) ? path : "/");} else {return requestUri;}}
}

    (getPathWithinServletMapping)首先会在配置的 DispatcherServlet 范围内查找,对于同一个请求 “http://localhost/a/b” 来说:

  • <url-pattern>/*</url-pattern>, 返回的就是 “/a/b”;
  • <url-pattern>/a/*</url-pattern>,那么返回的就是 “/b”。如果请求为 “http://localhost/a” ,那么返回的就是空字符串了。

    (getPathWithinApplication)只有在上一步返回空字符串时才会在 Application 范围内查找,对于用一个请求“http://localhost/context/a” 来说:

  • <Context path="/context">,返回的就是 “/a”;
  • <Context path="/">,返回的就是 “/context/a”。如果请求为 “http://localhost” ,那么返回的就是 “/”了。

 

AbstractHandlerMethodMapping 分支

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {@Overrideprotected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {// 同样使用 UrlPathHelper.getLookupPathForRequestString lookupPath = getUrlPathHelper().getLookupPathForRequest(request);if (logger.isDebugEnabled()) {logger.debug("Looking up handler method for path " + lookupPath);}this.mappingRegistry.acquireReadLock();try {// 根据请求 url找到对应的 HandlerMethodHandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);....// 省略日志// createWithResolvedBean目的是确保 HandlerMethod中持有的是被调用的实例 bean// 而不是 beanName,如果是会调用了 getBean获取实例后创建新的 HandleMethod// 因为这个实例 bean会用于反射调用方法return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);} finally {this.mappingRegistry.releaseReadLock();}}protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<Match>();// 使用 MappingRegistry.getMappingsByUrl,通过相对路径查找对应的 List<RequestMappingInfo>// 见 AbstractHandlerMethodMapping的注册逻辑List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {// 不为 null,说明 @RequestMapping指定的是非通配符路径// 找到匹配条件的填充 matchesaddMatchingMappings(directPathMatches, matches, request);}if (matches.isEmpty()) {// 如果通过直接路径找不到,就在所有注册的映射路径中查找// 找到匹配条件的填充 matchesaddMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}if (!matches.isEmpty()) {// 因为可能会有多个匹配方法,需要根据定义的优先级排序Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));Collections.sort(matches, comparator);if (logger.isTraceEnabled()) {logger.trace("Found " + matches.size() + " matching mapping(s) for [" +lookupPath + "] : " + matches);}// 取出最匹配的Match bestMatch = matches.get(0);if (matches.size() > 1) {// 条件:带有“Origin”、“Access-Control-Request-Method”请求头的 options请求if (CorsUtils.isPreFlightRequest(request)) {// 返回 EmptyHandler封装的 HandlerMethod// 调用会抛出异常 UnsupportedOperationExceptionreturn PREFLIGHT_AMBIGUOUS_MATCH;}// 有两个相同优先级的匹配结果,会抛异常Match secondBestMatch = matches.get(1);if (comparator.compare(bestMatch, secondBestMatch) == 0) {Method m1 = bestMatch.handlerMethod.getMethod();Method m2 = secondBestMatch.handlerMethod.getMethod();throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");}}// 调用 setAttribute设置一些属性handleMatch(bestMatch.mapping, lookupPath, request);// 返回匹配的 HandlerMethodreturn bestMatch.handlerMethod;} else {// 未匹配return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);}}
}

    同样使用的是 UrlPathHelper.getLookupPathForRequest 获取请求的相对路径。之后根据相对路径找到对应的 HandlerMethod 。涉及到了一个请求地址对应了多个匹配结果的筛选:

    private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {for (T mapping : mappings) {// 获取满足条件的 RequestMappingInfoT match = getMatchingMapping(mapping, request);// 只有满足返回的才不为 null,这一步会筛选掉不符合条件的// 例如:请求路径不匹配、header头不匹配等等if (match != null) {// 通过 MappingRegistry维护的 mappingLookup找到对应的 HandlerMethod// 使用 RequestMappingInfo和 HandlerMethod创建 Match// 用于 MatchComparator的排序matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));}}}
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {@Overrideprotected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {// 调用 RequestMappingInfo.getMatchingConditionreturn info.getMatchingCondition(request);}
}
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {@Overridepublic RequestMappingInfo getMatchingCondition(HttpServletRequest request) {RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);// 这一步基本不会,因为 @RequestMapping都有默认值if (methods == null || params == null || headers == null || consumes == null || produces == null) {return null;}// 对于 @RequestMapping指定的 path/value匹配(直接地址匹配、通配符匹配)PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);if (patterns == null) {return null;}// 对于 @RequestMapping指定的 method、param、header、consumes、produces的匹配RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);if (custom == null) {return null;}return new RequestMappingInfo(this.name, patterns,methods, params, headers, consumes, produces, custom.getCondition());}
}

    以上的源码,就是用满足请求条件的(指定的 method、header等) RequestMappingInfo 以及对应的 HandlerMethod 封装成 Match 并填充到 matches 的逻辑。

    这一步可能会筛选出多个匹配项,接下来就需要靠 MatchComparator 排序后挑选出最匹配项,逻辑见 RequestMappingInfo.compareTo,这里不展开分析。

 

getHandlerAdapter

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {// 遍历所有注册的 HandlerAdapterfor (HandlerAdapter ha : this.handlerAdapters) {if (logger.isTraceEnabled()) {logger.trace("Testing handler adapter [" + ha + "]");}// 调用 HandlerAdapter.supports看是否支持适配if (ha.supports(handler)) {return ha;}}}throw new ServletException("No adapter for handler [" + handler +"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");}

    这里调用的 supports 获取到支持适配的 HandlerAdapter,之后调用其 handle 方法执行处理逻辑,具体源码上篇已分析,见 “HandlerAdapter”。

    执行完之后,就获取到了 ModelAndView 返回,接着就是对视图的处理(processDispatchResult),放在下节分析。

 

总结

    本篇分析的就是一个请求从接收到处理的全过程,目前进度已经获取到了 ModelAndView,至于请求地址如何找到对应的 Handler、Handler 如何调用并返回 ModelAndView ,前几篇已作讲解。下篇将分析 “视图处理” 源码。

转载于:https://my.oschina.net/marvelcode/blog/1841027

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/538324.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

oracle中where中使用函数,Oracle 尽量避免在 SQL语句的WHERE子句中使用函数

-- Start在 WHERE 子句中应该尽量避免在列上使用函数&#xff0c;因为这样做会使该列上的索引失效&#xff0c;影响SQL 语句的性能。即使该列上没有索引&#xff0c;也应该避免在列上使用函数。考虑下面的情况&#xff1a;CREATE TABLE EMPLOYEE(NAME VARCHAR2(20) NOT NULL,--…

求近似数最值_干货|初中数学《数的开方》知识点梳理

本章内容课标的要求● 1.了解平方根、算术平方根、立方根的概念&#xff0c;会用根号表示数的平方根、算术平方根、立方根。● 2.了解乘方与开方互为逆运算&#xff0c;会用平方运算求百以内整数的平方根&#xff0c;会用立方运算会求百以内整数(对应的负整数)的立方根&#xf…

第三章(续)

目录 第二章 灰度变换与空间滤波(续)直方图处理与函数绘图生成直方图直方图均衡直方图匹配空间滤波线性空间滤波非线性空间滤波图像处理工具箱的标准滤波器线性空间滤波器非线性空间滤波器第二章 灰度变换与空间滤波(续) 直方图处理与函数绘图 生成直方图 应用函数 imhist 语法…

Linux Mysql 安装方法

1、检查是否有安装 [rootJDDB mysql]# yum list installed | grep mysql mysql-community-client.x86_64 5.6.39-2.el7 mysql56-community mysql-community-common.x86_64 5.6.39-2.el7 mysql56-community mysql-community…

oracle 经纬度算距离,根据经纬度诀别用java和Oracle存储过程计算两点距离

根据经纬度分别用java和Oracle存储过程计算两点距离create or replace procedure SP_GET_DISTANCE(cx in number,cy in number,sx in number, sy in number,distance out varchar2)isd number;x number;y number;r number;pi number;begin--开始计算r:6371229;--地球半径pi:3.1…

Kafka集群安装--测试--关闭

一、前提 1、kafka安装包下载&#xff1a;http://kafka.apache.org/downloads 2、jdk已安装 3、scala已安装 4、zookeeper集群已安装并运行二、步骤 1、对kafka_2.9.2-0.8.1.tgz进行解压缩&#xff1a;tar -zxvf kafka_2.9.2-0.8.1.tgz。2、对kafka目录进行改名&#xff1a;mv …

Java中的工厂模式

设计模式遵循原则 开闭原则&#xff1a;对扩展开放&#xff0c;对修改关闭里氏代换原则&#xff1a;只有当衍生类可以替换掉基类&#xff0c;软件单位的功能不受到影响时&#xff0c;基类才能真正被覆用。而衍生类也能够在基类的基础上增加新的行为依赖倒转原则&#xff1a;开闭…

python的底层实现_Python底层封装实现方法详解

这篇文章主要介绍了Python底层封装实现方法详解,文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下事实上&#xff0c;python封装特性的实现纯属“投机取巧”&#xff0c;之所以类对象无法直接调用私有方法和属性&a…

php 附近的距离,PHP查询附近的人及其距离的实现方法_PHP

本文实例讲述了PHP查询附近的人及其距离的实现方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;array(lat>$lat $dlat,lng>$lng-$dlng),right-top>array(lat>$lat $dlat, lng>$lng $dlng),left-bottom>array(lat>$lat - $dlat, lng>$ln…

统计指定目录下的视频时长

package time;import java.io.File;import org.apache.log4j.Logger;import it.sauronsoftware.jave.Encoder; import it.sauronsoftware.jave.EncoderException; import it.sauronsoftware.jave.MultimediaInfo;public class Test2 {/* 支持的后缀 */private static final Str…

怎么在cmd中运行python脚本_cmd中运行python脚本智能使用流程

(此时的ScaleMode自动变Vbuser)更有趣的是用来计算字串高、宽的TextHeight/TextWidth也变成以座标0-100的方式来表现了On Error Resume NextSet outstreemWscript.stdoutIf (LCase(Right(Wscript.fullname,11))"Wscript.exe") ThenSet objShellWscript.CreateObject(…

世界时钟 软件_Clocker for Mac(世界时钟软件)

Clocker for Mac是一款Mac平台上免费的世界时钟工具&#xff0c;方便我们查看世界各地的时间&#xff0c;它是开源免费的&#xff0c;完全没有广告。包括数百个时区&#xff0c;支持24小时制或AM / PM&#xff0c;macz提供Clocker mac免费版&#xff0c;欢迎前来下载&#xff0…

Mac 设置 NDK

2019独角兽企业重金招聘Python工程师标准>>> 1、首先查看我自己的android studio &#xff0c;找到以下路径 如上图&#xff0c;打开一个 AS 项目&#xff0c;file - project structure 这是我的3 个路径 Ndk /Users/dhbm/Library/Android/sdk/ndk-bundle Sdk /User…

Workbench has not been created yet

原因是&#xff1a;加载的插件变更后需要清理 在启动参数最后加入 -clean

oracle必须声明标识符函数,引用变量时需要必须声明标识符

SQL> declare2 pname emp.ename%type;3 psal emp.sal%type;4 begin5 select enmae,sal into pname,psal from emp where empno7782;6 dbms_output.put_line(pname||xsis||psal);7 end;8 /pname emp.ename%type;*第 2 行出现错误:ORA-06550: 第 2 行, 第 7 列:PLS-002…

四参数拟合曲线_每周放送|曲线拟合

曲线拟合No.1什么是曲线拟合所谓的曲线拟合&#xff0c;就是使用某一个模型(或者称为方程式)&#xff0c;将一系列的数据拟成平滑的曲线&#xff0c;以便观察两组数据之间的内在联系&#xff0c;了解数据之间的变化趋势。No.2曲线拟合的应用在数据分析时&#xff0c;我们有时需…

Spark集群运行jar包程序里的print日志哪里去了?

默认情况下&#xff0c;是输出到stdout里的。 方法一&#xff1a; 进入work所在机器的spark安装目录下的work目录&#xff0c;里面有日志输出。 方法二&#xff1a; 进入spark web ui 里 点击stdout就可以查看&#xff0c;如果没有可能在其他work上。

hibernate oracle clob 注解,Hibernate3.X实现基于CLOB字段类型的注解方式:

一&#xff1a;Hibernate3.X实现基于CLOB字段类型的注解方式的例子&#xff1a;下面直接上代码&#xff1a;二&#xff1a;UserInfo.javapackage cn.gov.csrc.cms.model;import javax.persistence.Basic;import javax.persistence.Column;import javax.persistence.Entity;impo…

Flutter下拉刷新,上拉加载更多数据

下拉刷新 很简单&#xff0c;直接使用 RefreshIndicator 组件&#xff0c; onRefresh 为重新获取数据的方法 Widget build(BuildContext context) {return Scaffold(body: Container(padding: EdgeInsets.all(2.0),child: RefreshIndicator(onRefresh: _refresh,backgroundColo…

qt 批量裁剪图片_照片变素描,不用下载App,好用的在线图片处理及图库

我们要处理图片时&#xff0c;无论是在电脑还是手机上&#xff0c;往往都需要下载软件&#xff0c;但如果你只是临时用一下的话&#xff0c;下载软件难免显得工程有点浩大。下面就推荐几个图片处理网站&#xff0c;打开网页就能用。1、图片处理 funny。pho。to这个网站提供了很…