SpringMVC 控制器方法(handler)的映射 - HandlerMapping
SpringMVC 控制器方法(handler)的映射 - HandlerMapping
简单源码分析
HandlerMapping
接口的作用是定义请求(request)和处理程序对象(handler)之间映射。
这个类可以由应用程序开发人员实现,当然这是没有必要的,因为框架中包含了 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
和 org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
,并且会自动注入 IOC 容器,自动在在 DispatcherServlet
的 doDispatch
方法中生效。
HandlerMapping
的实现可以支持拦截器,但不是必须这样做。处理程序总是被包装在 HandlerExecutionChain
实例中,可选地附带一些 HandlerInterceptor
实例。DispatcherServlet
将首先以给定的顺序调用每个 HandlerInterceptor
的 preHandle
方法,如果所有 preHandle 方法都返回 true,则最后调用处理程序本身。
具体的调用顺序请看《SpringMVC- 第二篇:控制器方法(handler)映射.md》中的
HandlerExecutionChain 和 DispatcherServlet中的源码分析
小节
参数化映射的能力是这个 MVC 框架的一个强大而不寻常的功能。例如,可以根据会话状态、cookie 状态或许多其他变量编写自定义映射。似乎没有其他 MVC 框架具有同样的灵活性。
HandlerMapping
有很多实现类,我们主要看 AbstractHandlerMapping
的子类。

接口源码分析:
HandlerMapping
的接口源码很有意思,除了只包含一个待实现的方法 getHandler
之外,还包含很多请求域属性名,在通过请求获取了 handler 之后,会将这个映射过程中的一些中间变量存放到这些请求域属性中,方便我们在控制器中来获取这些中间变量。
public interface HandlerMapping {
// 在处理器映射过程中最匹配的 handler ,也就是最终处理请求的 handler
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
// 请求路径中 用于匹配 handler的 部分路径 已弃用
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
// 处理器映射过程中使用的请求路径
// 注意,这个参数并不被所有的类型的HandlerMapping支持
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";
// 处理器映射过程中最匹配 请求路径的 控制器的路径模板
// 注意,这个参数并不被所有的类型的HandlerMapping支持
String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";
// (控制器)类级别的映射信息是否会被参考
// 注意,这个参数并不被所有的类型的HandlerMapping支持
String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";
// 获取请求路径参数
// 注意,这个参数并不被所有的类型的HandlerMapping支持
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
// 获取请求路径的矩阵参数
// 注意,这个参数并不被所有的类型的HandlerMapping支持
String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";
// 获取此处理器映射映射的handler可以生产的 MediaType 的集合
// 注意,这个参数并不被所有的类型的HandlerMapping支持
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
//
default boolean usesPathPatterns() {
return false;
}
// 根据请求中的信息,比如路径,session的状态,等返回包装了handler的 HandlerExecutionChain
@Nullable
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}
简单实践:
@RestController
@RequestMapping("/handlerMappingAttribute")
public class HandlerMappingAttributeTestController {
@RequestMapping("/allAtrtribute/**")
public String getAllAttribute(HttpServletRequest request){
Object handler = request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
System.out.println(handlerMethod.toString());
}
Object attribute = request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
System.out.println("用于映射的请求路径:"+attribute);
Object attribute1 = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
System.out.println("最匹配的handler的路径模板:"+attribute1);
Object attribute2 = request.getAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING);
Object attribute3 = request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
Object attribute4 = request.getAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE);
Object attribute5 = request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
return "";
}
}
访问链接 http://localhost:8080/SpringMVC_AnnotationConfig/handlerMappingAttribute/allAtrtribute
日志:
xyz.xiashuo.springmvcannotationconfig.controller.HandlerMappingAttributeTestController#getAllAttribute(HttpServletRequest)
用于映射的请求路径:/handlerMappingAttribute/allAtrtribute
最匹配的handler的路径模板:/handlerMappingAttribute/allAtrtribute/**
实际上,这几个属性里面,最好用的就是前三个
-
HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE
-
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE
-
HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE
与 HandlerInterceptor 的联系
具体请看《SpringMVC- 第二篇:控制器方法(handler)映射.md》中的
控制器方法(handler)拦截器
小节
HandlerMapping
接口中唯一的待实现方法 getHandler
的返回值类型是 HandlerExecutionChain
,那 HandlerMapping
是如何构造 HandlerExecutionChain
的呢?具体请看《SpringMVC- 第二篇:控制器方法(handler)映射.md》中的 拦截器的初始化流程
小节。
初始化
初始化
DispatcherServlet#initHandlerMappings
和 DispatcherServlet#getDefaultStrategies
DispatcherServlet#initHandlerMappings
请看《SpringMVC-DispatcherServlet 源码分析.md》的initStrategies
小节
在《SpringMVC- 第五篇:视图》就有过关于
DispatcherServlet#getDefaultStrategies
的研究。
一般用不到 DispatcherServlet#getDefaultStrategies
,通过注解配置 SpringMVC 的时候(实际上在 WebMvcConfigurationSupport
中),会自动注入在应用上下文中注入默认几个 HandlerMapping
的相应类型的 Bean,
-
RequestMappingHandlerMapping
-
BeanNameUrlHandlerMapping
-
SimpleUrlHandlerMapping
具体生效
doDispatch 执行流程
-
经过
HandlerMapping
进行处理器映射,获取 handler,实际上获取的是包含拦截器的处理器执行链HandlerExecutionChain
-
通过
HandlerExecutionChain
中最终的 handler,获取适配器HandlerAdapter
-
执行
HandlerAdapter#handle
方法执行处理器,获得ModelAndView
-
处理
ModelAndView
,如果有错误,就已经异常处理,渲染异常解析HandlerExceptionResolver
返回的ModelAndView
,如果没有错误,正常解析ModelAndView
在 DispatcherServlet
的 doDispatch
方法中,通过 getHandler(processedRequest);
获取 HandlerExecutionChain mappedHandler
DispatcherServlet
的 getHandler
方法则是通过遍历字段 handlerMappings
,调用 HandlerMapping
的 getHandler
方法,来获取控制器方法的执行链,找到了就返回,所以 handlerMappings
中的 HandlerMapping
的顺序很重要。
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
// 遍历 handlerMappings 字段,调用 HandlerMapping 的 getHandler 方法,获取控制器方法执行链,找到了就返回
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
// 获取控制器方法执行链,找到了就返回
// 所以 handlerMappings 的顺序很重要
return handler;
}
}
}
return null;
}
实际调试过程中,有三种
-
RequestMappingHandlerMapping
-
BeanNameUrlHandlerMapping
-
SimpleUrlHandlerMapping
可以看到 SimpleUrlHandlerMapping
排在 RequestMappingHandlerMapping
的后面,因此,如果 url 同时匹配控制器方法和 ResourceHttpRequestHandler
,那么会优先匹配控制器方法。
ResourceHttpRequestHandler
通常由SimpleUrlHandlerMapping
进行映射。
从请求路径到处理器的映射具体过程
《SpringMVC-第十篇:基于注解配置SpringMVC》
的 到底用哪一套逻辑呢?
小节,分析了在不同的 HandlerMapping
的实现中,进行从请求路径到控制器的映射的过程。非常值得一看,我们在具体的 HandlerMapping
的实现类的源码解析中也会提到这一部分的内容。具体的细节,请看 HandlerMapping
的实现类的源码解析
RequestMappingHandlerMapping
用于映射 @RequestMapping
表示的控制器方法这种类型的 handler。

关于
RequestMappingHandlerMapping
完整的初始化流程,请看《SpringMVC-RequestMappingHandlerMapping 源码解析.md》。
BeanNameUrlHandlerMapping
HandlerMapping
接口的实现,该接口将 url 映射到名称以斜杠 (/
) 开头的 bean 中,类似于 Struts
框架将 url 映射到 action 类的名称的方式。
连同 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
。这两个是 org.springframework.web.servlet.DispatcherServlet
使用的默认的 HandlerMapping
实现。此外,SimpleUrlHandlerMapping
允许声明式地自定义处理程序映射。(SimpleUrlHandlerMapping
也 DispatcherServlet
是默认使用的 HandlerMapping
实现)
映射是从 URL 到 bean 的名称。因此,一个传入的路径为 /foo
的 URL 将映射到一个名称为 /foo
的 handler bean,或者在多个路径映射到单个 handler 的情况下映射到 /foo
、/foo2
。
支持直接匹配 (给定 /test
->注册的 handler bean 的名称为 /test
) 和 *
通配符匹配 (给定 /test
->注册的 handler bean 的名称 /t*
)。注意,默认是在当前 servlet 映射路径中映射 (如果适用的话); 详见 AbstractHandlerMapping#alwaysUseFullPath
属性。有关模式选项的详细信息,请参见 org.springframework.util.AntPathMatcher
。
有关
AntPathMatcher
的具体解析,请参考《UrlPathHelper+PathMatcher 简单解析.md》
现在用的很少了,就不研究了
SimpleUrlHandlerMapping
SimpleUrlHandlerMapping
其实很简单,只用指定这个 HandlerMapping
需要处理的所有 URL 的匹配模式和单个 URL 匹配模式对应的处理器即可,用 urlMap 存起来,初始化的时候传入即可。
SimpleUrlHandlerMapping
的应用非常广泛,在 WebMvcConfigurationSupport#defaultServletHandlerMapping
、WebMvcConfigurationSupport#addViewControllers
、WebMvcConfigurationSupport#addResourceHandlers
中均有使用
将来我们需要自定义 HandlerMapping
用这个就是最方便的。
有关 RouterFunctionMapping 的疑问
有一个问题,明明 WebMvcConfigurationSupport
中的 bean,为什么在 SpringMVC 中没有生效,在 SpringBoot 中却生效了,还没搞清楚。?
自定义 HandlerMapping
当我们需要一些自定义的请求映射的时候,我们可以在容器中添加我们自定义的 HandlerMapping,注意设置 order 顺序
自定义 handlermapping 的版本的一个场景是,/api/v1 ,/api/v2 去不同的包下面找 handler,这个在《SpringMVC- 第十篇:基于注解配置 SpringMVC.md》中的 pathPrefixes 字段
小节中,已经实现了。