SpringBoot之请求映射原理

2024-06-14 1325阅读

前言

我们发出的请求,SpringMVC是如何精准定位到那个Controller以及具体方法?其实这都是 HandlerMapping 发挥的作用,这篇博文我们以 RequestMappingHandlerMapping 为例并结合源码一步步进行分析。

定义HandlerMapping

默认 HandlerMapping 主要定义在 EnableWebMvcConfiguration 和其祖父类 WebMvcConfigurationSupport 中

EnableWebMvcConfiguration
@Bean
@Primary
@Override
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
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
            FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
    WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(
            new TemplateAvailabilityProviders(applicationContext), applicationContext, getWelcomePage(),
            this.mvcProperties.getStaticPathPattern());
    welcomePageHandlerMapping.setInterceptors(getInterceptors(mvcConversionService, mvcResourceUrlProvider));
    welcomePageHandlerMapping.setCorsConfigurations(getCorsConfigurations());
    return welcomePageHandlerMapping;
}
WebMvcConfigurationSupport
@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping(
        @Qualifier("mvcConversionService") FormattingConversionService conversionService,
        @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
    mapping.setOrder(2);
    PathMatchConfigurer pathConfig = getPathMatchConfigurer();
    if (pathConfig.getPatternParser() != null) {
        mapping.setPatternParser(pathConfig.getPatternParser());
    }
    else {
        mapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
        mapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
    }
    mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
    mapping.setCorsConfigurations(getCorsConfigurations());
    return mapping;
}
@Bean
public RouterFunctionMapping routerFunctionMapping(
        @Qualifier("mvcConversionService") FormattingConversionService conversionService,
        @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    RouterFunctionMapping mapping = new RouterFunctionMapping();
    mapping.setOrder(3);
    mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
    mapping.setCorsConfigurations(getCorsConfigurations());
    mapping.setMessageConverters(getMessageConverters());
    PathPatternParser patternParser = getPathMatchConfigurer().getPatternParser();
    if (patternParser != null) {
        mapping.setPatternParser(patternParser);
    }
    return mapping;
}
@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(
        @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
        @Qualifier("mvcConversionService") FormattingConversionService conversionService,
        @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
    Assert.state(this.applicationContext != null, "No ApplicationContext set");
    Assert.state(this.servletContext != null, "No ServletContext set");
    PathMatchConfigurer pathConfig = getPathMatchConfigurer();
    ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
            this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());
    addResourceHandlers(registry);
    AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
    if (handlerMapping == null) {
        return null;
    }
    if (pathConfig.getPatternParser() != null) {
        handlerMapping.setPatternParser(pathConfig.getPatternParser());
    }
    else {
        handlerMapping.setUrlPathHelper(pathConfig.getUrlPathHelperOrDefault());
        handlerMapping.setPathMatcher(pathConfig.getPathMatcherOrDefault());
    }
    handlerMapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
    handlerMapping.setCorsConfigurations(getCorsConfigurations());
    return handlerMapping;
}

PS:WebMvcConfigurationSupport 中定义的 HandlerMapping 不止上述三个,但是有效的只有三个 (SpringBoot 版本 2.6.13),有的 HandlerMapping 需要满足一定条件才生效。

初始化

SpringBoot之请求映射原理

initHandlerMappings
private void initHandlerMappings(ApplicationContext context) {
    this.handlerMappings = null;
    if (this.detectAllHandlerMappings) {
        // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
        Map matchingBeans =
                BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerMappings = new ArrayList(matchingBeans.values());
            // We keep HandlerMappings in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerMappings);
        }
    }
    else {
        try {
            HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
            this.handlerMappings = Collections.singletonList(hm);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, we'll add a default HandlerMapping later.
        }
    }
    // Ensure we have at least one HandlerMapping, by registering
    // a default HandlerMapping if no other mappings are found.
    if (this.handlerMappings == null) {
        this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
    for (HandlerMapping mapping : this.handlerMappings) {
        if (mapping.usesPathPatterns()) {
            this.parseRequestPath = true;
            break;
        }
    }
}
detectAllHandlerMappings 属性是否为 true (默认为true)
  • true:获取 BeanFactory 中类型为 HandlerMapping 的 beans
  • false : 获取 BeanFactory 中类型为 HandlerMapping,beanName为 handlerMapping 的 bean
    如果 detectAllHandlerMappings 属性为 true,则会对查找到的 beans 进行排序,排序规则如下:
    1. HandlerMapping 是否继承 PriorityOrdered 接口,如果都继承 PriorityOrdered 接口,比较getOrder方法返回的值,值越小,优先级越高
    2. HandlerMapping 是否继承 Ordered 接口,如果都继承 Ordered接 口,比较 getOrder 方法返回的值,值越小,优先级越高
    3. HandlerMapping 所属class上是否存在 @Order 注解,如果存在,比较注解设置的值,值越小,优先级越高
    4. HandlerMapping 所属class上是否存在 @Priority 注解,如果存在,比较注解设置的值,值越小,优先级越高
    如果从 BeanFactory 中未获取到相关 HandlerMapping,则使用默认 HandlerMapping

    默认的 HandlerMapping 定义在 DispatcherServlet.properties 文件中

    SpringBoot之请求映射原理

    默认HandlerMapping为

    • BeanNameUrlHandlerMapping
    • RequestMappingHandlerMapping
    • RouterFunctionMapping

      请求映射

      DispatcherServlet#doDispatch

      SpringBoot之请求映射原理

      DispatcherServlet#getHandler

      SpringBoot之请求映射原理

      一共有五个 HandlerMapping (SpringBoot 版本 2.6.13,最新版本貌似有6个,大家自行验证一下),即我们上文所分析的在类EnableWebMvcConfiguration、WebMvcConfigurationSupport 中定义的 HandlerMapping

      如果某个 HandlerMapping 的 getHandler 方法返回了一个有效的 HandlerExecutionChain,我们就可以认为这个 HandlerMapping 可以处理这个请求。接下里以 RequestMappingHandlerMapping 为例进行分析,大部分请求也都是由这个 HandlerMapping 处理的

      PS : WelcomePageHandlerMapping 就是处理欢迎页的

      RequestMappingHandlerMapping的实例化

      RequestMappingHandlerMapping的类继承关系

      SpringBoot之请求映射原理

      通过上图,我们知道其祖父类(AbstractHandlerMethodMapping) 继承 InitializingBean 接口,继承 InitializingBean 接口的类会在bean的实例化过程中执行 afterPropertiesSet 方法

      AbstractHandlerMethodMapping#afterPropertiesSet

      SpringBoot之请求映射原理

      获取所有类型是 Object 的 beans,并且 beanName 不以 scopedTarget. 开头

      AbstractHandlerMethodMapping#processCandidateBean

      SpringBoot之请求映射原理

      根据 beanName 获取 beanType

      AbstractHandlerMethodMapping#isHandler

      SpringBoot之请求映射原理

      如果 beanType 上存在 @Controller 、@RequestMapping 注解则进行处理

      AbstractHandlerMethodMapping#detectHandlerMethods

      SpringBoot之请求映射原理

      主要有三个方法 selectMethods、getMappingForMethod、registerHandlerMethod

      1. selectMethods :遍历类中定义的方法以及接口实现方法(方法不能是合成的、桥接的,返回类型不能是Object ),如果某个方法执行接口函数(主体是 getMappingForMethod 方法)的返回值不为null,则将其放入一个类型为 Map 的 Map 中
      2. getMappingForMethod:如果方法存在 @RequestMapping (@GetMapping、@PostMapping、@PutMapping、@DeleteMapping 等)注解,则构建一个 RequestMappingInfo 对象
      3. registerHandlerMethod : 遍历 selectMethods 方法的返回结果(Map),将其注册到 AbstractHandlerMethodMapping 的 mappingRegistry 属性中

      PS : 经过测试,如果存在 @RequestMapping 注解,即使方法的描述符是 private,也可以接受请求

      RequestMappingHandlerMapping的getHandler方法

      AbstractHandlerMapping#getHandler

      SpringBoot之请求映射原理

      RequestMappingInfoHandlerMapping#getHandlerInternal

      SpringBoot之请求映射原理

      AbstractHandlerMethodMapping#getHandlerInternal

      SpringBoot之请求映射原理

      AbstractHandlerMethodMapping#lookupHandlerMethod

      SpringBoot之请求映射原理

      假设有一个UserController,明细由下方所示,我们来看一下这个 mappingRegistry 属性的结构

      @RestController
      public class UserController {
          @GetMapping("/user")
          public String getUser() {
              return "get user";
          }
          @PostMapping("/user")
          public String postUser() {
              return "post user";
          }
          @PutMapping("/user")
          public String putUser() {
              return "put user";
          }
          @DeleteMapping("/user")
          public String deleteUser() {
              return "delete user";
          }
      }

      SpringBoot之请求映射原理

      URI 和具体方法的映射关系,都存储在 mappingRegistry 这个属性中

      扩展:自定义HandlerMapping

      创建 CustomHandlerMapping
      public class CustomHandlerMapping implements HandlerMapping, PriorityOrdered {
          private ApplicationContext applicationContext;
          public CustomHandlerMapping(ApplicationContext applicationContext) {
              this.applicationContext = applicationContext;
          }
          @Override
          public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
              String beanName = request.getParameter("beanName");
              String methodName = request.getParameter("method");
              if (StringUtils.isBlank(beanName) || StringUtils.isBlank(methodName) || !applicationContext.containsBean(beanName)) {
                  return null;
              }
              Object object = applicationContext.getBean(beanName);
              Method method = null;
              Method[] declaredMethods = object.getClass().getDeclaredMethods();
              for (Method declaredMethod : declaredMethods) {
                  if (declaredMethod.getName().equals(methodName)) {
                      method = declaredMethod;
                      break;
                  }
              }
              if (method == null) {
                  return null;
              }
              HandlerMethod handlerMethod = new HandlerMethod(object, method);
              return new HandlerExecutionChain(handlerMethod);
          }
          @Override
          public int getOrder() {
              return 0;
          }
      }

      继承 PriorityOrdered 接口,让我们自定义的 HandlerMapping 优先级最高

      创建 HandlerMappingConfig
      @Configuration
      public class HandlerMappingConfig implements ApplicationContextAware {
          private ApplicationContext applicationContext;
          @Override
          public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
              this.applicationContext = applicationContext;
          }
          @Bean
          public CustomHandlerMapping customHandlerMapping() {
              return new CustomHandlerMapping(applicationContext);
          }
      }
      创建 CustomHandlerMappingController 
      @Component("chmc")
      public class CustomHandlerMappingController {
          @ResponseBody
          public String hello() {
              return "hello HandlerMapping! ";
          }
      }
      发送请求 localhost:8080?beanName=chmc&method=hello

      SpringBoot之请求映射原理

      自定义 handlerMapping 生效

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]