日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

SpringBoot中请求映射的原理(源码)

發布時間:2025/3/15 javascript 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 SpringBoot中请求映射的原理(源码) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、先看一下SpringMVC解析流程


時序圖:

二、SpringBoot請求映射原理

SpringBoot跟spring一脈相承,所以直接找DispatcherServlet這個類。
其繼承關系如下:

從此圖可以看出繼承樹,最終是來到HttpServlet的,也就是說必然會有doGetPost方法。而HttpServlet并沒有,于是順著關系找下去。

在FrameworkServlet中,我們發現了重寫了doGet/doPost的方法:

而doGet/doPost兩個方法都是調用processRequest的,進去看一眼,除了一些必要的初始化,最核心的就是doService方法了


而FrameworkServlet中doService是抽象的,那么再起子類必有實現,那么來到DispatcherServlet中找到此方法的實現:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {this.logRequest(request);Map<String, Object> attributesSnapshot = null;if (WebUtils.isIncludeRequest(request)) {attributesSnapshot = new HashMap();Enumeration attrNames = request.getAttributeNames();label95:while(true) {String attrName;do {if (!attrNames.hasMoreElements()) {break label95;}attrName = (String)attrNames.nextElement();} while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));attributesSnapshot.put(attrName, request.getAttribute(attrName));}}request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());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);}try {this.doDispatch(request, response);} finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {this.restoreAttributesAfterInclude(request, attributesSnapshot);}}}

在進行一大堆初始化之后,最核心的方法就是doDispatch(request, response),將請求進行轉發,這樣就意味著,每個請求的方法進來,都要經過這個方法,所以,SpringMVC功能分析都從 org.springframework.web.servlet.DispatcherServlet-》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 {try {ModelAndView mv = null;Object dispatchException = null;try {processedRequest = this.checkMultipart(request);multipartRequestParsed = processedRequest != request;// 找到當前請求使用哪個Handler(Controller的方法)處理mappedHandler = this.getHandler(processedRequest);if (mappedHandler == null) {this.noHandlerFound(processedRequest, response);return;}HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}this.applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception var20) {dispatchException = var20;} catch (Throwable var21) {dispatchException = new NestedServletException("Handler dispatch failed", var21);}this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);} catch (Exception var22) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);} catch (Throwable var23) {this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));}} finally {if (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else if (multipartRequestParsed) {this.cleanupMultipart(processedRequest);}}}

如何找到url對應的handler(controller)呢?就是下面這句:

// 找到當前請求使用哪個Handler(Controller的方法)處理mappedHandler = this.getHandler(processedRequest);

進入getHandler方法中發現其調用了getHandlerInternal(request)方法,進行處理后獲得url又調用了lookupHandlerMethod我們查看這個方法,這個方法最后找到handler返回:

debug到mapping.getHandler(request)時,發現調的是getHandlerInternal(request)方法,進行處理后獲得url又調用了lookupHandlerMethod我們查看這個方法,這個方法最后找到handler返回

getHandler.getHandlerInternal:

@Nullablepublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {Object handler = this.getHandlerInternal(request);if (handler == null) {handler = this.getDefaultHandler();}if (handler == null) {return null;} else {if (handler instanceof String) {String handlerName = (String)handler;handler = this.obtainApplicationContext().getBean(handlerName);}HandlerExecutionChain executionChain = this.getHandlerExecutionChain(handler, request);if (this.logger.isTraceEnabled()) {this.logger.trace("Mapped to " + handler);} else if (this.logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {this.logger.debug("Mapped to " + executionChain.getHandler());}if (this.hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {CorsConfiguration config = this.getCorsConfiguration(handler, request);if (this.getCorsConfigurationSource() != null) {CorsConfiguration globalConfig = this.getCorsConfigurationSource().getCorsConfiguration(request);config = globalConfig != null ? globalConfig.combine(config) : config;}if (config != null) {config.validateAllowCredentials();}executionChain = this.getCorsHandlerExecutionChain(request, executionChain, config);}return executionChain;}}

lookupHandlerMethod:

public class AbstractHandlerMapping{protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<>();List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);//從mapping中找到url匹配的項放入matches中,這里會過濾掉,get和post的不同請求if (directPathMatches != null) {addMatchingMappings(directPathMatches, matches, request);}//沒有匹配遍歷所有mapping查找if (matches.isEmpty()) {// No choice but to go through all mappings...addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}if (!matches.isEmpty()) {Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));matches.sort(comparator);Match bestMatch = matches.get(0);//確保匹配的url只有一個,或者是最佳匹配的if (matches.size() > 1) {if (logger.isTraceEnabled()) {logger.trace(matches.size() + " matching mappings: " + matches);}if (CorsUtils.isPreFlightRequest(request)) {return PREFLIGHT_AMBIGUOUS_MATCH;}Match secondBestMatch = matches.get(1);if (comparator.compare(bestMatch, secondBestMatch) == 0) {Method m1 = bestMatch.handlerMethod.getMethod();Method m2 = secondBestMatch.handlerMethod.getMethod();String uri = request.getRequestURI();throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");}}request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);handleMatch(bestMatch.mapping, lookupPath, request);return bestMatch.handlerMethod;}else {return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);}} }

本項目中,debug發現,handlerMapping中會有五個值

而RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射規則:

我們在來看靜態資源匹配(/**或/webjar匹配),他是和上面不同的mapping,他是SimpleUrlHandlerMapping,會調用AbstractUrlHandlerMapping,的getHandlerInternal方法,調用lookupHandler方法找到handler返回

public class AbstractUrlHandlerMapping{protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {// Direct match?//這里得到 /** 或/webjars/**,如果是/**或/webjars/**直接得到handlerObject handler = this.handlerMap.get(urlPath);if (handler != null) {// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}validateHandler(handler, request);return buildPathExposingHandler(handler, urlPath, urlPath, null);}// Pattern match?//模式匹配到具體的/xxx或/webjar/xxxList<String> matchingPatterns = new ArrayList<>();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 + "/");}}}//找到所有匹配的模式串中最匹配的(比如要匹配/webjars/xx,/**也會匹配到)String bestMatch = null;Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);if (!matchingPatterns.isEmpty()) {matchingPatterns.sort(patternComparator);if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {logger.trace("Matching patterns " + matchingPatterns);}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 + "]");}}// Bean name or resolved handler?if (handler instanceof String) {String handlerName = (String) handler;handler = obtainApplicationContext().getBean(handlerName);}//驗證handler類型validateHandler(handler, request);String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);// There might be multiple 'best patterns', let's make sure we have the correct URI template variables// for all of themMap<String, String> uriTemplateVariables = new LinkedHashMap<>();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);}}if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {logger.trace("URI variables " + uriTemplateVariables);}return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);}// No handler found...return null;} }
  • 順便講歡迎頁的原理:

? 請求進來,挨個嘗試所有的HandlerMapping看是否有請求信息。
? 如果有就找到這個請求對應的handler
? 如果沒有就是下一個 HandlerMapping

如果你啥也沒傳,也就是"/",那么在RequestMappingHandlerMapping中將不會找到合適的,然后他就會循環到下一個控制器:WelcomePageHandlerMapping:會調用AbstractUrlHandlerMapping的getHandlerInternal方法,調用lookupHandler獲得handler返回空(這里匹配上面那種),繼續執行

public class AbstractUrlHandlerMapping{protected Object getHandlerInternal(HttpServletRequest request) throws Exception {String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);request.setAttribute(LOOKUP_PATH, lookupPath);Object handler = lookupHandler(lookupPath, request);//這里返回為空if (handler == null) {// We need to care for the default handler directly, since we need to// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.Object rawHandler = null;//這里可以匹配到 / if ("/".equals(lookupPath)) { rawHandler = getRootHandler();}if (rawHandler == null) {rawHandler = getDefaultHandler();}if (rawHandler != null) {// Bean name or resolved handler?if (rawHandler instanceof String) {String handlerName = (String) rawHandler;rawHandler = obtainApplicationContext().getBean(handlerName);}validateHandler(rawHandler, request);handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);}}return handler;} }


而這個控制器就是專門處理"/"的,于是根據處理,轉發到index.html中。
(SpringBoot自動配置歡迎頁的 WelcomePageHandlerMapping 。訪問 /能訪問到index.html;)

三、總結

這里默認有兩種實現:AbstractUrlHandlerMapping,AbstractHandlerMethodMapping,兩種第一種是資源路徑獲得handler(如/**,/webjars/,welcome頁也可以算在這里),第二種是通過@RequestMapping注解下的方法實現的路徑
查看代碼的過程中會發現他總會進行一次最佳路徑匹配,不同的具體實現可以達到不同效果
這樣可以保證匹配的都是最佳路徑,比如在匹配/webjars/的時候會匹配到/**和/webjars/,那么最佳匹配就可以篩選出/webjars/路徑
還可以保證路徑唯一,比如在requestMapping請求匹配多個請求時,若匹配到多個請求會拋異常

  • 所有的請求映射都在HandlerMapping中。
  • 不管是什么方式匹配都會調用getHandlerInternal,然后根據不同類型的Mapping,對getHandlerInternal的實現方式不同來根據情況獲得不同的handler這里默認有兩種實現:AbstractUrlHandlerMapping,AbstractHandlerMethodMapping,資源路徑獲得handler(如/**,/webjars/,welcome頁也可以算在這里)
  • SpringBoot自動配置歡迎頁的 WelcomePageHandlerMapping 。訪問 /能訪問到index.html;
  • SpringBoot自動配置了默認的 RequestMappingHandlerMapping,通過@RequestMapping注解下的方法實現的路徑查看代碼的過程中會發現他總會進行一次最佳路徑匹配,不同的具體實現可以達到不同效果這樣可以保證匹配的都是最佳路徑,比如在匹配/webjars/的時候會匹配到/**和/webjars/,那么最佳匹配就可以篩選出/webjars/路徑還可以保證路徑唯一,比如在requestMapping請求匹配多個請求時,若匹配到多個請求會拋異常
  • 請求進來,挨個嘗試所有的HandlerMapping看是否有請求信息。
  • 如果有就找到這個請求對應的handler
  • 如果沒有就是下一個 HandlerMapping
  • 我們需要一些自定義的映射處理,我們也可以自己給容器中放HandlerMapping。自定義 HandlerMapping
  • 總結

    以上是生活随笔為你收集整理的SpringBoot中请求映射的原理(源码)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。