【老王读SpringMVC-2】url 与 controller method 的映射关系注册

上文提到,如果我们自己要实现 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 内置了两个常用的实现: BeanNameUrlHandlerMappingRequestMappingHandlerMapping
用户可以编写自定义的 HandlerMapping,并通过实现 org.springframework.core.Ordered 来指定优先级。

下面我们来看下 HandlerMapping 的类图:

HandlerMapping.png

SpringBoot 默认注册的 HandlerMapping 有:

  • RequestMappingHandlerMapping – 处理 @RequestMapping 注解的 url 与处理程序的映射 (最常用)
  • BeanNameUrlHandlerMapping – 将 / 开头的 beanName 与 url 进行映射
  • RouterFunctionMapping
  • SimpleUrlHandlerMapping – 处理普通的 url
  • WelcomePageHandlerMapping – 处理首页

RequestMappingHandlerMapping 注册映射关系

在 SpringMVC 中,我们最常用的定义端点接口的方式是使用 @RequestMapping
所以,我们主要来研究一下注解形式的 url 是如何进行映射关系注册的?

在 SpringMVC 中,RequestMappingHandlerMapping 是用来支持 @RequestMapping 注解形式的 url 的。
映射关系的注册是在它的父类 AbstractHandlerMethodMapping 中完成的:

registerHandlerMethod.png

可以看到,当 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() 是负责具体的注册逻辑的:
register.png

可以看到,这里注册了 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 中

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老王学源码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值