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

一、先看一下SpringMVC解析流程

在这里插入图片描述
时序图:
在这里插入图片描述

二、SpringBoot请求映射原理

SpringBoot跟spring一脉相承,所以直接找DispatcherServlet这个类。

其继承关系如下:
在这里插入图片描述
在这里插入图片描述

从此图可以看出继承树,最终是来到HttpServlet的,也就是说必然会有doGetPost方法。而HttpServlet并没有,于是顺着关系找下去。

在FrameworkServlet中,我们发现了重写了doGet/doPost的方法:

在这里插入图片描述
而doGet/doPost两个方法都是调用processRequest的,进去看一眼,除了一些必要的初始化,最核心的就是doService方法

img
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;

// 找到当前请求使用哪个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)呢?就是下面这句:

1
2
// 找到当前请求使用哪个Handler(Controller的方法)处理
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);
//从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返回

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 {
// Direct match?
//这里得到 /** 或/webjars/**,如果是/**或/webjars/**直接得到handler
Object 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/xxx
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 + "/");
}
}
}

//找到所有匹配的模式串中最匹配的(比如要匹配/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 them
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);
}

// No handler found...
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) {
// 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请求匹配多个请求时,若匹配到多个请求会抛异常

  1. 所有的请求映射都在HandlerMapping中。
  2. 不管是什么方式匹配都会调用getHandlerInternal,然后根据不同类型的Mapping,对getHandlerInternal的实现方式不同来根据情况获得不同的handler这里默认有两种实现:AbstractUrlHandlerMapping,AbstractHandlerMethodMapping,资源路径获得handler(如/**,/webjars/,welcome页也可以算在这里)
  3. SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
  4. SpringBoot自动配置了默认的 RequestMappingHandlerMapping,通过@RequestMapping注解下的方法实现的路径查看代码的过程中会发现他总会进行一次最佳路径匹配,不同的具体实现可以达到不同效果这样可以保证匹配的都是最佳路径,比如在匹配/webjars/的时候会匹配到/**和/webjars/,那么最佳匹配就可以筛选出/webjars/路径还可以保证路径唯一,比如在requestMapping请求匹配多个请求时,若匹配到多个请求会抛异常
  5. 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
  6. 如果有就找到这个请求对应的handler
  7. 如果没有就是下一个 HandlerMapping
  8. 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping