老王读SpringMVCurl 与 controller method 的映射关系注册

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了老王读SpringMVCurl 与 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 的类图:

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 中

补充: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);

小结

在 SpringMVC 中,request 与 handler method 的请求关系注册和映射都是通过 HandlerMapping 来完成的。
HandlerMapping 的实现类中最常用的是 RequestMappingHandlerMapping,它是用来处理 @RequestMapping 注解形式的请求关系映射的。
映射关系的注册是在它的父类 AbstractHandlerMethodMapping#registerHandlerMethod() 中完成的。
这里注册了 4 种信息:
1、将 directPath 注册到 pathLookup 中
2、将 HandlerMethod 的 name 注册到 nameLookup 中
3、将 HandlerMethod 注册到 corsLookup 中(处理跨域请求映射)
4、将所有的 RequestMappingInfo 全部都注册到 registry 中

以上是关于老王读SpringMVCurl 与 controller method 的映射关系注册的主要内容,如果未能解决你的问题,请参考以下文章

老王读Spring Transaction-6spring-tx与DataSource连接池整合的原理

#yyds干货盘点# 老王读Spring AOP-4Spring AOP 与Spring IoC 结合的过程 && ProxyFactory 解析

#yyds干货盘点# 老王读Spring AOP-5@Transactional产生AOP代理的原理

#yyds干货盘点# 老王读Spring AOP-5@Transactional产生AOP代理的原理

#yyds干货盘点# 老王读Spring AOP-1Pointcut如何匹配到 join point

老王读Spring Transaction-3TransactionDefinition原理和源码解析