Spring WebFlux是如何映射请求到控制器的?

一、前言

Spring Flux中的核心DispatcherHandler的处理过程分为三步,其中首步就是通过HandlerMapping接口查找Request所对应的Handler。本文就是通过阅读源码的方式,分析一下HandlerMapping接口的实现者之一——RequestMappingHandlerMapping类,用于处理基于注解的路由策略,把所有用@Controller和@RequestMapping标记的类中的Handler识别出来,以便DispatcherHandler调用的。

HandlerMapping接口的另外两种实现类:1、RouterFunctionMapping用于函数式端点的路由;2、SimpleUrlHandlerMapping用于显式注册的URL模式与WebHandler配对。

文章系列

  • 关于非阻塞IO:《从时间碎片角度理解阻塞IO模型及非阻塞模型》
  • 关于Spring WebFlux新手入门:《快速上手Spring Flux框架》
  • 为什么Spring要引入SpringFlux框架 尚未完成
  • Spring WebFlux是如何映射请求到控制器的? 本文
  • Spring WebFlux中执行HandlerMapping的过程 尚未完成
  • Spring WebFlux中是如何处理HandlerResult的 尚未完成
  • Spring WebFlux与WEB服务器之Servlet 3.1+ 尚未完成
  • Spring WebFlux与WEB服务器之Netty 尚未完成

二、对基于注解的路由控制器的抽象

Spring中基于注解的控制器的使用方法大致如下:

@Controller
public class MyHandler{
    @RequestMapping("/")
    public String handlerMethod(){

    }
}

在Spring WebFlux中,对上述使用方式进行了三层抽象模型。

  1. Mapping

    • 用户定义的基于annotation的映射关系
    • 该抽象对应的类是:org.springframework.web.reactive.result.method.RequestMappingInfo
    • 比如上述例子中的 @RequestMapping("/")所代表的映射关系
  2. Handler

    • 代表控制器的类
    • 该抽象对应的类是:java.lang.Class
    • 比如上述例子中的MyHandler类
  3. Method

    • 具体处理映射的方法
    • 该抽象对应的类是:java.lang.reflect.Method
    • 比如上述例子中的String handlerMethod()方法

基于上述三层抽象模型,进而可以作一些组合。

  1. HandlerMethod

    • Handler与Method的结合体,Handler(类)与Method(方法)搭配后就成为一个可执行的单元了
  2. Mapping vs HandlerMethod

    • 把Mapping与HandlerMethod作为字典存起来,就可以根据请求中的关键信息(路径、头信息等)来匹配到Mapping,再根据Mapping找到HandlerMethod,然后执行HandlerMethod,并传递随请求而来的参数。

理解了这个抽象模型后,接下来分析源码来理解Spring WebFlux如何处理请求与Handler之间的Mapping关系时,就非常容易了。

HandlerMapping接口及其各实现类负责上述模型的构建与运作。

三、HandlerMapping接口实现的设计模式

HandlerMapping接口实现,采用了"模版方法"这种设计模式。

1层:AbstractHandlerMapping implements HandlerMapping, Ordered, BeanNameAware

  ^  
  |  

2层:AbstractHandlerMethodMapping implements InitializingBean

  ^  
  |  

3层:RequestMappingInfoHandlerMapping

  ^  
  |  

4层:RequestMappingHandlerMapping implements EmbeddedValueResolverAware

下面对各层的职责作简要说明:

  • 第1层主要实现了对外提供模型的接口

    • 即重载了HandlerMapping接口的"Mono getHandler(ServerWebExchange exchange) "方法,并定义了骨架代码。
    • 第2层有两个责任 —— 解析用户定义的HandlerMethod + 实现对外提供模型接口实现所需的抽象方法

      • 通过实现了InitializingBean接口的"void afterPropertiesSet()"方法,解析用户定义的Handler和Method。
      • 实现第1层对外提供模型接口实现所需的抽象方法:"Mono getHandlerInternal(ServerWebExchange exchange)"
    • 第3层提供根据请求匹配Mapping模型实例的方法
    • 第4层实现一些高层次用到的抽象方法来创建具体的模型实例。
    • 小结一下,就是HandlerMapping接口及其实现类,把用户定义的各Controller等,抽象为上述的Mapping、Handler及Method模型,并将Mapping与HandlerMethod作为字典关系存起来,还提供通过匹配请求来获得HandlerMethod的公共方法。

      接下来的章节,将先分析解析用户定义的模型并缓存模型的过程,然后再分析一下匹配请求来获得HandlerMethod的公共方法的过程。

      四、解析用户定义的模型并缓存模型的过程

      4-1、实现InitializingBean接口

      第2层AbstractHandlerMethodMapping抽象类中的一个重要方法——实现了InitializingBean接口的"void afterPropertiesSet()"方法,为Spring WebFlux带来了解析用户定义的模型并缓存模型的机会 —— Spring容器初初始化完成该类的具体类的Bean后,将会回调这个方法。
      在该方法中,实现获取用户定义的Handler、Method、Mapping以及缓存Mapping与HandlerMethod映射关系的功能。

      @Override
      public void afterPropertiesSet() {
      
          initHandlerMethods();
          
          // Total includes detected mappings + explicit registrations via registerMapping..
          ...
      }

      4-2、找到用户定义的Handler

      afterPropertiesSet方法中主要是调用了void initHandlerMethods()方法,具体如下:

      protected void initHandlerMethods() {
          //获取Spring容器中所有Bean名字
          String[] beanNames = obtainApplicationContext().getBeanNamesForType(Object.class);
      
          for (String beanName : beanNames) {
              if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
                  Class beanType = null;
                  try {
                      //获取Bean的类型
                      beanType = obtainApplicationContext().getType(beanName);
                  }
                  catch (Throwable ex) {
                      // An unresolvable bean type, probably from a lazy bean - let's ignore it.
                      if (logger.isTraceEnabled()) {
                          logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
                      }
                  }
                  
                  //如果获取到类型,并且类型是Handler,则继续加载Handler方法。
                  if (beanType != null && isHandler(beanType)) {
                      detectHandlerMethods(beanName);
                  }
              }
          }
          
          //初始化后收尾工作
          handlerMethodsInitialized(getHandlerMethods());
      }

      这儿首先获取Spring容器中所有Bean名字,然后循环处理每一个Bean。如果Bean名称不是以SCOPED_TARGET_NAME_PREFIX常量开头,则获取Bean的类型。如果获取到类型,并且类型是Handler,则继续加载Handler方法。

      isHandler(beanType)调用,检查Bean的类型是否符合handler定义。
      AbstractHandlerMethodMapping抽象类中定义的抽象方法"boolean isHandler(Class beanType)",是由RequestMappingHandlerMapping类实现的。具体实现代码如下:

      protected boolean isHandler(Class beanType) {
          return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
                  AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
      }

      不难看出,对于RequestMappingHandlerMapping这个实现类来说,只有拥有@Controller或者@RequestMapping注解的类,才是Handler。(言下之意对于其他实现类来说Handler的定义不同)。

      具体handler的定义,在HandlerMapping各实现类来说是不同的,这也是isHandler抽象方法由具体实现类来实现的原因。

      4-3、发现Handler的Method

      接下来我们要重点看一下"detectHandlerMethods(beanName);"这个方法调用。

      protected void detectHandlerMethods(final Object handler) {
          Class handlerType = (handler instanceof String ?
                  obtainApplicationContext().getType((String) handler) : handler.getClass());
      
          if (handlerType != null) {
              //将handlerType转换为用户类型(通常等同于被转换的类型,不过诸如CGLIB生成的子类会被转换为原始类型)
              final Class userType = ClassUtils.getUserClass(handlerType);
              //寻找目标类型userType中的Methods,selectMethods方法的第二个参数是lambda表达式,即感兴趣的方法的过滤规则
              Map methods = MethodIntrospector.selectMethods(userType,
                      //回调函数metadatalookup将通过controller定义的mapping与手动定义的mapping合并起来
                      (MethodIntrospector.MetadataLookup) method -> getMappingForMethod(method, userType));
              if (logger.isTraceEnabled()) {
                  logger.trace("Mapped " + methods.size() + " handler method(s) for " + userType + ": " + methods);
              }
              methods.forEach((key, mapping) -> {
                  //再次核查方法与类型是否匹配
                  Method invocableMethod = AopUtils.selectInvocableMethod(key, userType);
                  //如果是满足要求的方法,则注册到全局的MappingRegistry实例里
                  registerHandlerMethod(handler, invocableMethod, mapping);
              });
          }
      }

      首先将参数handler(即外部传入的BeanName或者BeanType)转换为Class类型变量handlerType。如果转换成功,再将handlerType转换为用户类型(通常等同于被转换的类型,不过诸如CGLIB生成的子类会被转换为原始类型)。接下来获取该用户类型里所有的方法(Method)。循环处理每个方法,如果是满足要求的方法,则注册到全局的MappingRegistry实例里。

      4-4、解析Mapping信息

      其中,以下代码片段有必要深入探究一下

       Map methods = MethodIntrospector.selectMethods(userType,
                      (MethodIntrospector.MetadataLookup) method -> getMappingForMethod(method, userType));

      MethodIntrospector.selectMethods方法的调用,将会把用@RequestMapping标记的方法筛选出来,并交给第二个参数所定义的MetadataLookup回调函数将通过controller定义的mapping与手动定义的mapping合并起来。
      第二个参数是用lambda表达式传入的,表达式中将method、userType传给getMappingForMethod(method, userType)方法。

      getMappingForMethod方法在高层次中是抽象方法,具体的是现在第4层RequestMappingHandlerMapping类中实现。在具体实现getMappingForMethod时,会调用到RequestMappingHandlerMapping类的下面这个方法。从该方法中,我们可以看到,首先会获得参数element(即用户在Controller中定义的方法)的RequestMapping类型的类实例,然后构造代表Mapping抽象模型的RequestmappingInfo类型实例并返回。

      private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
              RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
              RequestCondition condition = (element instanceof Class ?
                      getCustomTypeCondition((Class) element) : getCustomMethodCondition((Method) element));
              return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
          }

      构造代表Mapping抽象模型的RequestmappingInfo类型实例,用的是createRequestMappingInfo方法,如下。可以看到RequestMappingInfo所需要的信息,包括paths、methods、params、headers、consumers、produces、mappingName,即用户定义@RequestMapping注解时所设定的可能的参数,都被存在这儿了。拥有了这些信息,当请求来到时,RequestMappingInfo就可以测试自身是否是处理该请求的人选之一了。

      protected RequestMappingInfo createRequestMappingInfo(
                  RequestMapping requestMapping, @Nullable RequestCondition customCondition) {
      
              RequestMappingInfo.Builder builder = RequestMappingInfo
                      .paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
                      .methods(requestMapping.method())
                      .params(requestMapping.params())
                      .headers(requestMapping.headers())
                      .consumes(requestMapping.consumes())
                      .produces(requestMapping.produces())
                      .mappingName(requestMapping.name());
              if (customCondition != null) {
                  builder.customCondition(customCondition);
              }
              return builder.options(this.config).build();
          }

      4-5、缓存Mapping与HandlerMethod关系

      最后,registerHandlerMethod(handler, invocableMethod, mapping)调用将缓存HandlerMethod,其中mapping参数是RequestMappingInfo类型的。。
      内部调用的是MappingRegistry实例的void register(T mapping, Object handler, Method method)方法,其中T是RequestMappingInfo类型。
      MappingRegistry类维护所有指向Handler Methods的映射,并暴露方法用于查找映射,同时提供并发控制。

      public void register(T mapping, Object handler, Method method) {
                  this.readWriteLock.writeLock().lock();
                  try {
                      HandlerMethod handlerMethod = createHandlerMethod(handler, method);
                      ......
                      this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
                  }
                  finally {
                      this.readWriteLock.writeLock().unlock();
                  }
              }

      五、匹配请求来获得HandlerMethod

      AbstractHandlerMethodMapping类的“Mono getHandlerInternal(ServerWebExchange exchange)”方法,具体实现了根据请求查找HandlerMethod的逻辑。

          @Override
          public Mono getHandlerInternal(ServerWebExchange exchange) {
              //获取读锁
              this.mappingRegistry.acquireReadLock();
              try {
                  HandlerMethod handlerMethod;
                  try {
                      //调用其它方法继续查找HandlerMethod
                      handlerMethod = lookupHandlerMethod(exchange);
                  }
                  catch (Exception ex) {
                      return Mono.error(ex);
                  }
                  if (handlerMethod != null) {
                      handlerMethod = handlerMethod.createWithResolvedBean();
                  }
                  return Mono.justOrEmpty(handlerMethod);
              }
              //释放读锁
              finally {
                  this.mappingRegistry.releaseReadLock();
              }
          }

      handlerMethod = lookupHandlerMethod(exchange)调用,继续查找HandlerMethod。我们继续看一下HandlerMethod lookupHandlerMethod(ServerWebExchange exchange)方法的定义。为方便阅读,我把注释也写在了代码里。

          protected HandlerMethod lookupHandlerMethod(ServerWebExchange exchange) throws Exception {
              List matches = new ArrayList<>();
              //查找所有满足请求的Mapping,并放入列表mathes
              addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, exchange);
      
              if (!matches.isEmpty()) {
                  //获取比较器comparator
                  Comparator comparator = new MatchComparator(getMappingComparator(exchange));
                  //使用比较器将列表matches排序
                  matches.sort(comparator);
                  //将排在第1位的作为最佳匹配项
                  Match bestMatch = matches.get(0);
                  if (matches.size() > 1) {
                      //将排在第2位的作为次佳匹配项
                      Match secondBestMatch = matches.get(1);
                  }
                  handleMatch(bestMatch.mapping, bestMatch.handlerMethod, exchange);
                  return bestMatch.handlerMethod;
              }
              else {
                  return handleNoMatch(this.mappingRegistry.getMappings().keySet(), exchange);
              }
          }

      六、总结

      理解了Spring WebFlux在获取映射关系方面的抽象设计模型后,就很容易读懂代码,进而更加理解框架的具体处理方式,在使用框架时做到“知己知彼”。

      原文:http://www.yesdata.net/2018/11/27/spring-flux-request-mapping/

      你可能感兴趣的:(reactor,springboot,spring-mvc,spring)