07HandlerMapping中用到的RequestMappingInfo和RequestCondition

简介: RequestCondition(请求匹配条件)体系。上面提到的RequestMapping注解申明的属性与之呼应的就是spring中RequestCondition的实现体系。 RequestMappingInfo体系RequestMappingInfo是请求映射信息的描述,维护了一个请求所匹配的各种条件。即一个请求是有很多匹配条件的都放在了RequestMappingInfo中 RequestMappingInfo的生成、存放、和获取

spring体系看起来复杂的原因之一就是其内部有很多为了解决某类问题而提取并实现的模型。在初始化HandlerMapping(RequestMappingHandlerMapping)时,要将RequestMapping注解申明的属性:请求路径、请求方法、请求头、请求参数、生产消费的MIME等存放起来,这些声明的属性信息不仅可以单独作用还可以相互结合作用。比如:同一个请求路径,不同的请求方法就是两个不同的业务场景。


内容概览


  • RequestCondition(请求匹配条件)体系。
    上面提到的RequestMapping注解申明的属性与之呼应的就是spring中RequestCondition的实现体系。
  • RequestMappingInfo体系
    RequestMappingInfo是请求映射信息的描述,维护了一个请求所匹配的各种条件。即一个请求是有很多匹配条件的都放在了RequestMappingInfo中
  • RequestMappingInfo的生成、存放、和获取


RequestCondition


一个舒适的模式就是根据一个业务场景定义出一个功能接口,然后一个或多个抽象类实现该接口并完成主要的功能,最后就是具体实现类根据各自特点实现自己的特征。


接口定义描述


public interface RequestCondition<T> {


   /**

    * Combine this condition with another such as conditions from a type-level and

    * method-level {@code RequestMapping} annotation.

    */

   T combine(T other);


   /**

    * Check if the condition matches the request returning a potentially new

    * instance created for the current request. For example a condition with

    * multiple URL patterns may return a new instance only with those patterns that

    * match the request.

    */

   @Nullable

   T getMatchingCondition(HttpServletRequest request);

}


  • combine方法就是多个条件相互结合的
  • getMatchingCondition方法就是检查当前条件的实现与当前请求是否匹配


类关系



这里列举了三个具体的实现实际上并没有全部使用。


PatternsRequestCondition的创建


PatternsRequestCondition的创建是随着创建RequestMappingInfo的创建而创建的。在RequestMappingInfo的内部类DefaultBuilder的build方法里


       @Override

       public RequestMappingInfo build() {


           PathPatternsRequestCondition pathPatterns = null;

           PatternsRequestCondition patterns = null;


           if (null != this.options.patternParser) {

               pathPatterns = (ObjectUtils.isEmpty(this.paths)) ?

                       EMPTY_PATH_PATTERNS : new PathPatternsRequestCondition(this.options.patternParser, this.paths);

           } else {

               //1.创建PatternsRequestCondition

               patterns = (ObjectUtils.isEmpty(this.paths)) ?

                       EMPTY_PATTERNS :

                       new PatternsRequestCondition(this.paths, null, this.options.getPathMatcher(), false, false, null);

           }


           return new RequestMappingInfo(this.mappingName, pathPatterns, patterns);

       }


  1. 这里走的是else逻辑。为什么是else逻辑,这个在创建RequestMappingInfo时会得出
  2. this.paths就是RequestMapping里声明的接口url。所以正常情况下不会为空即使用paths创建PatternsRequestCondition然后再创建RequestMappingInfo
  3. this.options也是RequestMapping中的一个内部类BuilderConfiguration,是创建RequestMappingInfo必须的一个内部类


这个过程就是在RequestMapping时,通过构造方法传递设置的PatternsRequestCondition。


RequestMappingInfo


RequestMappinginfo的类结构图



创建RequestMappinginfo


RequestMappingHandlerMapping的createRequestMappingInfo


创建RequestMappingInfo是在RequestMappingHandlerMapping类中创建的,是在注册Handler阶段,可以翻下04篇的描述。


private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();


protected RequestMappingInfo createRequestMappingInfo(RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition{

       RequestMappingInfo.Builder builder = RequestMappingInfo

               .paths(requestMapping.path())

               .mappingName(requestMapping.name());


       if (null != customCondition) {

           builder.customCondition(customCondition);

       }

       return builder.options(this.config).build();

   }


由其类结构图可以知道是采用Builder模式创建。


RequestMappingHandlerMapping#DefaultBuilder的build方法


       public RequestMappingInfo build() {


           PathPatternsRequestCondition pathPatterns = null;

           PatternsRequestCondition patterns = null;


           if (null != this.options.patternParser) {

               pathPatterns = (ObjectUtils.isEmpty(this.paths)) ?

                       EMPTY_PATH_PATTERNS : new PathPatternsRequestCondition(this.options.patternParser, this.paths);

           } else {

                //1.创建PatternsRequestCondition

               patterns = (ObjectUtils.isEmpty(this.paths)) ?

                       EMPTY_PATTERNS :

                       new PatternsRequestCondition(this.paths, null, this.options.getPathMatcher(), false, false, null);

           }


           return new RequestMappingInfo(this.mappingName, pathPatterns, patterns);

       }


在构造者模式的build方法里。默认设置传递的options的patternParser属性是空,所以走else逻辑块。


创建RequestMappingInfo的参数patterns,然后调用构造方法创建。


RequestMappingInfo的构造方法


   @Nullable

   private final String name;


   @Nullable

   private final PathPatternsRequestCondition pathPatternsCondition;


   @Nullable

   private final PatternsRequestCondition patternsCondition;



   public RequestMappingInfo(@Nullable String name,

                             @Nullable PathPatternsRequestCondition pathPatternsCondition, @Nullable PatternsRequestCondition patternsCondition) {

       Assert.isTrue(pathPatternsCondition != null || patternsCondition != null,

               "Neither PathPatterns nor String patterns condition");


       this.name = StringUtils.hasText(name) ? name : null;

       this.pathPatternsCondition = pathPatternsCondition;

       this.patternsCondition = patternsCondition;

   }


还剩余的两个方法


  • getDirectPaths
  • getMatchingCondition


这两个方法在获取Handler时会使用


RequestMappingInfo的维护


RequestMappingInfo的维护是想表达,创建的RequestMappingInfo怎么存放,使用的时候怎么获取。RequestMappingInfo是在AbstractHandlerMethodMapping的系列内部类中维护的。


RequestMappingInfo的创建是在声明HandlerMapping阶段创建的,因为维护也在这个过程内。


类关系图



  1. MappingRegistry相当于是入口,在register方法被调用时进行存放映射信息
  2. registry存放RequestMappingInfo和MappingRegistration的映射
  3. pathLookup是个一对多的关系,存放了请求路径和RequestMappingInfo的关系
  4. nameLoopup也是个一对多的关系,存放的是name和HandlerMethod的关系


注册阶段


       public void register(T mapping, Object handler, Method method) {

           this.readWriteLock.writeLock().lock();


           try {

               //1.根据Controller类和RequestMapping方法创建一个HandlerMethod

               HandlerMethod handlerMethod = createHandlerMethod(handler, method);

               //2.校验handlerMethod合法性

               validateMethodMapping(handlerMethod, mapping);

               //3.获取mapping也就是RequestMappingInfo中的path,也是RequestMapping注解声明的URL

               Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);

               //4.将URL和mapping放入一个map里,实际上是LinkedMultiValueMap

               for (String path : directPaths) {

                   this.pathLookup.add(path, mapping);

               }

               String name = null;

               if (null != getNamingStrategy()) {

                   name = getNamingStrategy().getName(handlerMethod, mapping);

                   addMappingName(name, handlerMethod);

               }

               //5.将mapping(RequestMappingInfo)和MappingRegistration的关系放入map里

               this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name, false));

           } finally {

               this.readWriteLock.writeLock().unlock();

           }


       }


  1. 根据Controller类和RequestMapping方法创建一个HandlerMethod
  2. 校验handlerMethod合法性,比如是否有重复等
  3. 获取mapping也就是RequestMappingInfo中的path,也是RequestMapping注解声明的URL
  4. 将URL和mapping放入一个map里,实际上是LinkedMultiValueMap
  1. 这里的LinkedMultiValueMap是spring内部实现的一个map,是个key:list的一对多结构
  1. 将mapping(RequestMappingInfo)和MappingRegistration的关系放入map里


获取阶段


AbstractHandlerMethodMapping的lookupHandlerMethod方法


   @Nullable

   protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {

       List<Match> matches = new ArrayList<>();

       //1.根据请求路径获取RequestMappingInfo

       List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);

       if (null != directPathMatches) {

           addMatchingMapping(directPathMatches, matches, request);

       }

       if (matches.isEmpty()) {

           addMatchingMapping(this.mappingRegistry.getRegistration().keySet(), matches, request);

       }

       if (!matches.isEmpty()) {

           Match bestMatch = matches.get(0);


           request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());

           handleMatch(bestMatch.mapping, lookupPath, request);

           return bestMatch.getHandlerMethod();

       } else {

           return handleNoMatch(this.mappingRegistry.getRegistration().keySet(), lookupPath, request);

       }

   }


RequestMappingInfo#MappingRegistry的getMappingsByDirectPath方法


       @Nullable

       public List<T> getMappingsByDirectPath(String urlPath) {

           return this.pathLookup.get(urlPath);

       }


AbstractHandlerMethodMapping的addMatchingMapping


   private void addMatchingMapping(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {

       //2.遍历当前URL获取到的RequestMappingInfo

       for (T mapping : mappings) {

           //3.调用获取新的RequestMappingInfo

           T match = getMatchingMapping(mapping, request);

           if (null != match) {

               //4.将匹配的内容放到集合里,this.mappingRegistry.getRegistration().get(mapping)

               matches.add(new Match(match, this.mappingRegistry.getRegistration().get(mapping)));

           }

       }

   }


RequestMappingInfo的getMatchingCondition方法


   @Override

   public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {


       PathPatternsRequestCondition pathPatterns = null;

       if (null != this.pathPatternsCondition) {

           pathPatterns = this.pathPatternsCondition.getMatchingCondition(request);

           if (null == pathPatterns) {

               return null;

           }

       }


       PatternsRequestCondition patterns = null;

       if (null != this.patternsCondition) {

           patterns = this.patternsCondition.getMatchingCondition(request);

           if (null == patterns) {

               return null;

           }

       }

       return new RequestMappingInfo(this.name, pathPatterns, patterns);

   }


获取阶段是在根据当前请求获取Handler阶段中发生的,获取Handler的流程可以翻看06篇内容。


  1. 根据请求路径也就是URL从map里获取存放的RequestMappingInfo
  2. 循环遍历
  3. 最终还是回到了RequestMappingInfo的getMatchingCondition方法。根据存放的信息进行匹配过滤,如果这个方法返回了null,对业务来说就是没有找到匹配的Handler


其他


这个过程是不仅又些复杂,同时也有很多细节功能。比如AntPathMatcher和PathPattern;以及UrlPathHelper中的各个工具方法;还有注册获取过程中在request的请求域内放置的缓存信息等。还是值得多看几遍的。

目录
打赏
0
0
0
0
25
分享
相关文章
【Java笔记+踩坑】SpringMVC基础
springmvc简介、入门案例、bean加载控制、PostMan工具的使用、普通和JSON和日期格式请求参数传递、响应JSON或jsp或文本、Rest风格
【Java笔记+踩坑】SpringMVC基础
SpringBoot2.6.x 整合swagger3.0 报错Failed to start bean ‘documentationPluginsBootstrapper‘
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
15474 3
SpringBoot2.6.x 整合swagger3.0 报错Failed to start bean ‘documentationPluginsBootstrapper‘
@ControllerAdvice:你可以没用过,但是不能不了解
`@ControllerAdvice` 是 Spring MVC 中用于定义全局行为的注解,如异常处理、数据绑定和预处理。它从 `@Component` 派生,确保被扫描并纳入容器。`@ExceptionHandler` 用于全局异常处理,提供统一的错误响应。例如,当处理不当的异常时,它能返回友好的错误信息。`@InitBinder` 在数据绑定前对参数进行处理,如格式转换。`@ModelAttribute` 可以用于全局绑定模型属性,如登录用户信息。Spring MVC 通过 `DispatcherServlet` 和 `HandlerAdapter` 在请求处理流程中应用这些全局配置。
358 3
@ControllerAdvice:你可以没用过,但是不能不了解
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
本文介绍了如何在Spring Boot项目中集成Swagger 2.x和3.0版本,并提供了解决Swagger在Spring Boot中启动失败问题“Failed to start bean ‘documentationPluginsBootstrapper’; nested exception is java.lang.NullPointerEx”的方法,包括配置yml文件和Spring Boot版本的降级。
springboot 集成 swagger 2.x 和 3.0 以及 Failed to start bean ‘documentationPluginsBootstrapper‘问题的解决
|
12月前
|
spring,springBoot配置类型转化器Converter以及FastJsonHttpMessageConverter,StringHttpMessageConverter 使用
spring,springBoot配置类型转化器Converter以及FastJsonHttpMessageConverter,StringHttpMessageConverter 使用
927 1
【Java】已解决java.util.concurrent.CancellationException异常
【Java】已解决java.util.concurrent.CancellationException异常
442 0
SpringMVC中单独配置<mvc:default-servlet-handler/> 导致 Controller失效
SpringMVC中单独配置<mvc:default-servlet-handler/> 导致 Controller失效
412 0
已解决错误代码: MethodArgumentTypeMismatchException(方法参数类型不匹配异常)
已解决错误代码: MethodArgumentTypeMismatchException(方法参数类型不匹配异常)
Springboot中JUNIT5单元测试+Mockito详解
Springboot中JUNIT5单元测试+Mockito详解
1228 1
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问