上文提到,如果我们自己要实现 spring mvc 框架的话,大致需要实现如下功能:
- 0、将 url 与 Controller method 的对应关系进行注册
- 1、通过请求的 url 找到 Controller method (即 url 与 Controller method 的映射)
- 2、将请求参数进行绑定,即将入参绑定到 Controller method 的参数对象上
- 3、执行处 Controller method (即 HandlerAdapter#handle())
- 4、对 Controller method 的返回值进行处理
4.1 如果正常返回的话,对返回值对象进行处理(即 ReturnValueHandler)
包括:如果返回视图 View 的话,对视图进行渲染 (即 ViewResolver)
4.2 如果有异常返回的话,对异常进行处理(即 @ExceptionHandler)
下面我们就来研究一下,Spring MVC是如何将 url 与 controller method 的映射关系找出来进行注册的?
分析 url 与 handler method 的映射关系的注册
经过前面对 DispatcherServlet#doDispatch()
的分析,我们知道断点应该打在获取 HandlerExecutionChain
的地方。
/**
* 将所有的 HandlerMapping 按顺序遍历一次,获取 request 对应的 HandlerExecutionChain。
*/
@Nullable
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;
}
可以看到,在获取 HandlerExecutionChain
时,Spring 会将所有的 HandlerMapping
按顺序遍历一次。
而在 Spring 中有许多 HandlerMapping
,最常用的当属 RequestMappingHandlerMapping
。
org.springframework.web.servlet.HandlerMapping
HandlerMapping
是用来定义 request 和处理程序(handler)之间的映射关系的。
Spring 内置了两个常用的实现: BeanNameUrlHandlerMapping
和 RequestMappingHandlerMapping
。
用户可以编写自定义的 HandlerMapping,并通过实现 org.springframework.core.Ordered
来指定优先级。
下面我们来看下 HandlerMapping
的类图:
SpringBoot 默认注册的 HandlerMapping 有:
- RequestMappingHandlerMapping – 处理 @RequestMapping 注解的 url 与处理程序的映射 (最常用)
- BeanNameUrlHandlerMapping – 将 / 开头的 beanName 与 url 进行映射
- RouterFunctionMapping
- SimpleUrlHandlerMapping – 处理普通的 url
- WelcomePageHandlerMapping – 处理首页
RequestMappingHandlerMapping 注册映射关系
在 SpringMVC 中,我们最常用的定义端点接口的方式是使用 @RequestMapping
。
所以,我们主要来研究一下注解形式的 url 是如何进行映射关系注册的?
在 SpringMVC 中,RequestMappingHandlerMapping
是用来支持 @RequestMapping
注解形式的 url 的。
映射关系的注册是在它的父类 AbstractHandlerMethodMapping
中完成的:
可以看到,当 RequestMappingHandlerMapping
这个 bean 在加载的时候,会调用父类的 AbstractHandlerMethodMapping#afterPropertiesSet()
来完成 url 与 handler method 映射关系的注册。
具体的注册是由 AbstractHandlerMethodMapping.MappingRegistry#registry()
来完成的。
判断哪些类是 handler:RequestMappingHandlerMapping#isHandler()
/**
* bean class 上如果有 @Controller 或 @RequestMapping 的话,就认为是一个 handler 处理程序
*/
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
具体的 register 注册逻辑
request 和 handler method 映射关系的注册是由 AbstractHandlerMethodMapping.MappingRegistry
来实现的。
MappingRegistry
的定义如下:
class MappingRegistry {
// 保存所有的 <RequestMappingInfo, MappingRegistration>
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
// 保存 RequestMappingInfo 中拥有 directPath 的: <directPath, RequestMappingInfo>
private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
// 保存所有的 HandlerMethod name: <name, List<HandlerMethod>>
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
// 保存拥有 cors 配置的 HandlerMethod: <HandlerMethod, CorsConfiguration>
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
.....
}
AbstractHandlerMethodMapping.MappingRegistry#register()
是负责具体的注册逻辑的:
可以看到,这里注册了 4 种信息:
1、将 directPath 注册到 pathLookup 中
2、将 HandlerMethod 的 name 注册到 nameLookup 中
3、将 HandlerMethod 注册到 corsLookup 中(处理跨域请求映射)
4、将所有的 RequestMappingInfo 全部都注册到 registry 中
directPath: 指那些没有特殊字符的 path。特殊字符:?、*、{}
补充:SpringBoot 中 RequestMappingHandlerMapping bean 是在哪里定义的?
WebMvcAutoConfiguration.EnableWebMvcConfiguration#requestMappingHandlerMapping()
中定义的:
// org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration#requestMappingHandlerMapping
@Bean
@Primary
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
resourceUrlProvider);
}
找到 bean 定义处的技巧:
断点打在 bean class 的构造函数或初始化方法里面,当断点进入时,可以很方便的从调用堆栈中找到相应的 BeanDefinition 的值,BeanDefinition 中就记录了这个 bean 是从在哪里定义的。
如果断点打不到 bean class 里面的话,那么就可以在 applicationContext 中获取相应的 BeanDefinition,再查看 bean 定义的地方。
核心就是要找到 bean 对应的 BeanDefinition。
小结
在 SpringMVC 中,request 与 handler method 的请求关系注册和映射都是通过 HandlerMapping
来完成的。
HandlerMapping
的实现类中最常用的是 RequestMappingHandlerMapping
,它是用来处理 @RequestMapping
注解形式的请求关系映射的。
映射关系的注册是在它的父类 AbstractHandlerMethodMapping#registerHandlerMethod()
中完成的。
这里注册了 4 种信息:
1、将 directPath 注册到 pathLookup 中
2、将 HandlerMethod 的 name 注册到 nameLookup 中
3、将 HandlerMethod 注册到 corsLookup 中(处理跨域请求映射)
4、将所有的 RequestMappingInfo 全部都注册到 registry 中