SpringBoot中请求映射的原理(源码) 池慕睡不着 2025-05-06 2025-05-06 时序图:
二、SpringBoot请求映射原理 SpringBoot跟spring一脉相承,所以直接找DispatcherServlet
这个类。
其继承关系如下:
从此图可以看出继承树,最终是来到HttpServlet的,也就是说必然会有doGetPost方法。而HttpServlet并没有,于是顺着关系找下去。
在FrameworkServlet中,我们发现了重写了doGet/doPost的方法:
而doGet/doPost两个方法都是调用processRequest
的,进去看一眼,除了一些必要的初始化,最核心的就是doService方法
了
而FrameworkServlet中doService是抽象的
,那么再起子类必有实现,那么来到DispatcherServlet
中找到此方法的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 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()开始分析进去这个方法看一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 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; 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)呢?就是下面这句:
1 2 mappedHandler = this .getHandler(processedRequest);
进入getHandler方法中发现其调用了getHandlerInternal(request)方法,进行处理后获得url又调用了lookupHandlerMethod我们查看这个方法,这个方法最后找到handler返回: debug到mapping.getHandler(request)时,发现调的是getHandlerInternal(request)
方法,进行处理后获得url又调用了lookupHandlerMethod
我们查看这个方法,这个方法最后找到handler返回
getHandler.getHandlerInternal:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Nullable public 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class AbstractHandlerMapping { protected HandlerMethod lookupHandlerMethod (String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList <>(); List<T> directPathMatches = this .mappingRegistry.getMappingsByUrl(lookupPath); if (directPathMatches != null ) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { 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 ); 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返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 public class AbstractUrlHandlerMapping { protected Object lookupHandler (String urlPath, HttpServletRequest request) throws Exception { Object handler = this .handlerMap.get(urlPath); if (handler != null ) { if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); return buildPathExposingHandler(handler, urlPath, urlPath, null ); } List<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 + "/" ); } } } 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 + "]" ); } } if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath); Map<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); } return null ; } }
顺便讲欢迎页的原理: • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。 • 如果有就找到这个请求对应的handler • 如果没有就是下一个 HandlerMapping
如果你啥也没传,也就是”/“,那么在RequestMappingHandlerMapping中将不会找到合适的,然后他就会循环到下一个控制器:WelcomePageHandlerMapping:会调用AbstractUrlHandlerMapping的getHandlerInternal方法,调用lookupHandler获得handler返回空(这里匹配上面那种),继续执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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 ) { Object rawHandler = null ; if ("/" .equals(lookupPath)) { rawHandler = getRootHandler(); } if (rawHandler == null ) { rawHandler = getDefaultHandler(); } if (rawHandler != null ) { 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