javascript
SpringBoot中请求映射的原理(源码)
一、先看一下SpringMVC解析流程
時序圖:
二、SpringBoot請求映射原理
SpringBoot跟spring一脈相承,所以直接找DispatcherServlet這個類。
其繼承關(guān)系如下:
從此圖可以看出繼承樹,最終是來到HttpServlet的,也就是說必然會有doGetPost方法。而HttpServlet并沒有,于是順著關(guān)系找下去。
在FrameworkServlet中,我們發(fā)現(xiàn)了重寫了doGet/doPost的方法:
而doGet/doPost兩個方法都是調(diào)用processRequest的,進(jìn)去看一眼,除了一些必要的初始化,最核心的就是doService方法了
而FrameworkServlet中doService是抽象的,那么再起子類必有實現(xiàn),那么來到DispatcherServlet中找到此方法的實現(xiàn):
在進(jìn)行一大堆初始化之后,最核心的方法就是doDispatch(request, response),將請求進(jìn)行轉(zhuǎn)發(fā),這樣就意味著,每個請求的方法進(jìn)來,都要經(jīng)過這個方法,所以,SpringMVC功能分析都從 org.springframework.web.servlet.DispatcherServlet-》doDispatch()開始分析進(jìn)去這個方法看一下:
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;// 找到當(dāng)前請求使用哪個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對應(yīng)的handler(controller)呢?就是下面這句:
// 找到當(dāng)前請求使用哪個Handler(Controller的方法)處理mappedHandler = this.getHandler(processedRequest);進(jìn)入getHandler方法中發(fā)現(xiàn)其調(diào)用了getHandlerInternal(request)方法,進(jìn)行處理后獲得url又調(diào)用了lookupHandlerMethod我們查看這個方法,這個方法最后找到handler返回:
debug到mapping.getHandler(request)時,發(fā)現(xiàn)調(diào)的是getHandlerInternal(request)方法,進(jìn)行處理后獲得url又調(diào)用了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發(fā)現(xiàn),handlerMapping中會有五個值
而RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射規(guī)則:
我們在來看靜態(tài)資源匹配(/**或/webjar匹配),他是和上面不同的mapping,他是SimpleUrlHandlerMapping,會調(diào)用AbstractUrlHandlerMapping,的getHandlerInternal方法,調(diào)用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;} }- 順便講歡迎頁的原理:
? 請求進(jìn)來,挨個嘗試所有的HandlerMapping看是否有請求信息。
? 如果有就找到這個請求對應(yīng)的handler
? 如果沒有就是下一個 HandlerMapping
如果你啥也沒傳,也就是"/",那么在RequestMappingHandlerMapping中將不會找到合適的,然后他就會循環(huán)到下一個控制器:WelcomePageHandlerMapping:會調(diào)用AbstractUrlHandlerMapping的getHandlerInternal方法,調(diào)用lookupHandler獲得handler返回空(這里匹配上面那種),繼續(xù)執(zhí)行
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;} }
而這個控制器就是專門處理"/"的,于是根據(jù)處理,轉(zhuǎn)發(fā)到index.html中。
(SpringBoot自動配置歡迎頁的 WelcomePageHandlerMapping 。訪問 /能訪問到index.html;)
三、總結(jié)
這里默認(rèn)有兩種實現(xiàn):AbstractUrlHandlerMapping,AbstractHandlerMethodMapping,兩種第一種是資源路徑獲得handler(如/**,/webjars/,welcome頁也可以算在這里),第二種是通過@RequestMapping注解下的方法實現(xiàn)的路徑
查看代碼的過程中會發(fā)現(xiàn)他總會進(jìn)行一次最佳路徑匹配,不同的具體實現(xiàn)可以達(dá)到不同效果
這樣可以保證匹配的都是最佳路徑,比如在匹配/webjars/的時候會匹配到/**和/webjars/,那么最佳匹配就可以篩選出/webjars/路徑
還可以保證路徑唯一,比如在requestMapping請求匹配多個請求時,若匹配到多個請求會拋異常
總結(jié)
以上是生活随笔為你收集整理的SpringBoot中请求映射的原理(源码)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 方法论:如何从0到1搭建一套完整的邀请体
- 下一篇: 互联网晚报 | 1月11日 星期二 |