HandlerMapping 用于将请求映射到 handler,以及前置处理拦截器和后置处理拦截器。该映射基于某些规则,其中的细节随着 HandlerMapping 的实现有所不同。
两个主要的 HandlerMapping 的实现类:
(1) RequestMappingHandlerMapping ,它支持 @RequestMapping 注解的方法
(2) SimpleUrlHandlerMapping,它维护 URI 路径模式到 handler 的指定注册
Spring Boot参考官方文档 1.1.2. Special Bean Types
默认情况下,Spring Boot 提供 5 个默认的 HandlerMapping,他们分别是:
- RequestMappingHandlerMapping
- BeanNameUrlHandlerMapping
- RouterFunctionMapping
- SimpleUrlHandlerMapping
- WelcomePageHandlerMapping
上述这几个 HandlerMapping 都是在 WebMvcAutoConfiguration 内部类 EnableWebMvcConfiguration 中配置的。
为了能够通过类名立即能知道该类实现了 HandlerMapping 接口,这些实现类都以 HandlerMapping 为结尾,以前缀标识其作用。
如何找到当前请求使用哪个 HandlerMapping 处理?
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
AbstractHandlerMapping
AbstractHandlerMapping 是所有 HandlerMapping 的父类,以下是所有请求获取 handler 的统一入口。
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// getHandlerInternal 是抽象方法,留给子类实现
Object handler = getHandlerInternal(request);
// 如果子类无法找到 handler 则使用默认 handler
if (handler == null) {
handler = getDefaultHandler();
}
// 如果没有默认 handler,则返回 null
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
getHandlerInternal 是抽象方法,由子类实现,因此官方才会有这样一句话:
RequestMappingHandlerMappingThe mapping is based on some criteria, the details of which vary by HandlerMapping implementation.
RequestMappingHandlerMapping 是 RequestMappingInfoHandlerMapping 的子类。RequestMappingInfoHandlerMapping 实现了 getHandlerInternal:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
try {
return super.getHandlerInternal(request);
}
finally {
ProducesRequestCondition.clearMediaTypesAttribute(request);
}
}
RequestMappingInfoHandlerMapping 并没有做什么具体的操作,他只是调用父类 AbstractHandlerMethodMapping 的 getHandlerInternal 方法:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
上述代码比较重要的是 lookupHandlerMethod 方法调用,它负责寻找 handler 方法:
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List matches = new ArrayList<>();
// 先通过 url 找到匹配项
List directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
// 接着添加匹配的映射(根据具体条件,如 HTTP Method 等)
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
// 如果匹配项不止 1 个
if (matches.size() > 1) {
Comparator comparator = new MatchComparator(getMappingComparator(request));
// 排序,取出第 0 个
matches.sort(comparator);
bestMatch = matches.get(0);
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);
}
}
上述代码具体做的就是找到一个最佳匹配 handlerMethod 并返回,如果有两个并列的最佳匹配 handlerMethod,则抛出异常,Spring 无法决定选择哪个,因此,建议不要定义具有歧义的 handler。
SimpleUrlHandlerMapping尽管类名称叫 SimpleUrlHandlerMapping,但是其 beanName 为 resourceHandlerMapping
SimpleUrlHandlerMapping 是 AbstractUrlHandlerMapping 的子类,直接使用其 getHandlerInternal 方法:
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
// 获得需要寻找的路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
// 寻找 handler
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;
}
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
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?
List 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 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);
}
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 uriTemplateVariables = new LinkedHashMap<>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, matchingPattern) == 0) {
Map vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map 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;
}



