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.pswp.cn/news/538324.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/538324.shtml
英文地址,請注明出處:http://en.pswp.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這個網站提供了很…