之前的文章里,介绍了DispatcherSerlvet处理请求的流程。
其中一个核心的步骤是:请求地址映射,即根据request获取对应的HandlerExcecutionChain
为了后续的请求地址映射,在项目初始化时,需要先将request-handler映射关系缓存起来。
HandlerMapping有很多实现类,比如RequestMappingHandlerMappingBeanNameUrlHandlerMappingRouterFunctionMapping,它们分别对应不同的Controller接口定义规则。
这篇文章要介绍的是RequestMappingHandlerMapping请求地址映射的初始化流程。

大家看到RequestMappingHandlerMapping可能会感到陌生。
实际上,它是我们日常打交道最多的HandlerMapping实现类:它是@Controller@RequestMapping的底层实现。
RequestMappingHanlderMapping初始化时,会根据@Controller@RequestMapping创建RequestMappingInfo,将request-handler映射关系缓存起来。

首先,我们简单来看一下RequestMappingHandlerMapping的类图:
RequestMappingHandlerMapping请求地址映射的初始化流程!-LMLPHP

RequestMappingHandlerMapping实现了InitializingBean接口。
在Spring容器设置完所有bean的属性,以及执行完XxxAware接口的setXxx()方法后,会触发InitializingBeanafterPropertiesSet()方法。
AbstractHandlerMethodMappingafterPropertiesSet()方法中,会完成请求地址映射的初始化流程:

public void afterPropertiesSet() {  
   initHandlerMethods();  
}

AbstractHandlerMethodMappinginitHandlerMethods方法中,会遍历容器中所有bean进行处理:

protected void initHandlerMethods() {  
    // 1、遍历所有bean的名称
   for (String beanName : getCandidateBeanNames()) {  
      if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {  
        // 2、解析bean
         processCandidateBean(beanName);  
      }  
   }  
   handlerMethodsInitialized(getHandlerMethods());  
}

AbstractHandlerMethodMappingprocessCandidateBean方法中,会对bean进行筛选。如果该bean的类对象中包含@ControllerRequestMapping注解,会进一步遍历该类对象的各个方法:

protected void processCandidateBean(String beanName) {  
   Class<?> beanType = null;  
   try {  
      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);  
      }  
   }  
   // 1、判断bean的类对象是否包含@Controller或@RequestMapping
   if (beanType != null && isHandler(beanType)) {  
      // 2、构造request-handler映射信息
      detectHandlerMethods(beanName);  
   }  
}

RequestMappingHandlerMappingisHandler()方法中,会判断当前类对象是否包含@Controller@RequestMapping注解:

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

AbstractHandlerMethodMappingdetectHandlerMethods方法中,会构造并缓存request-handler信息:

protected void detectHandlerMethods(Object handler) {  
   Class<?> handlerType = (handler instanceof String ?  
         obtainApplicationContext().getType((String) handler) : handler.getClass());  
  
   if (handlerType != null) {  
      Class<?> userType = ClassUtils.getUserClass(handlerType);  
      // 1、遍历类对象的各个方法,返回Method-RequestMappingInfo映射
      Map<Method, T> methods = MethodIntrospector.selectMethods(userType,  
            (MethodIntrospector.MetadataLookup<T>) method -> {  
               try {  
	               // 2、构造request-handler请求地址映射
                  return getMappingForMethod(method, userType);  
               }  
               catch (Throwable ex) {  
                  throw new IllegalStateException("Invalid mapping on handler class [" +  
                        userType.getName() + "]: " + method, ex);  
               }  
            });  
      if (logger.isTraceEnabled()) {  
         logger.trace(formatMappings(userType, methods));  
      }  
      else if (mappingsLogger.isDebugEnabled()) {  
         mappingsLogger.debug(formatMappings(userType, methods));  
      }  
      // 3、缓存request-handler请求地址映射
      methods.forEach((method, mapping) -> {  
         Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);  
         registerHandlerMethod(handler, invocableMethod, mapping);  
      });  
   }  
}

MethodIntrospectorselectMethods()方法中,会遍历类对象各个方法,调用RequestMappingHandlerMappinggetMappingForMethod()方法,构造request地址信息:

  • 如果该方法满足书写规则,即含有@RequestMapping,会返回RequestMappingInfo对象
  • 如果该方法不满足书写规则,会返回null

MethodIntrospectorselectMethods()方法会将所有request地址信息不为nullMethod-RequestMappingInfo映射返回。

RequestMappingHandlerMappinggetMappingForMethod()方法中,会构造完整的request地址信息。主要包括以下步骤:

  1. 构造方法级别的request地址信息
  2. 构造类级别的request地址信息
  3. 整合两个级别的request地址信息,构造出完整的request地址信息

RequestMappingHandlerMappinggetMappingForMethod()方法源码如下:

protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
	// 1、构造方法级别的request-handler信息
   RequestMappingInfo info = createRequestMappingInfo(method);  
   if (info != null) {  
	   // 2、构造类级别的request-handler信息
      RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);  
      if (typeInfo != null) {  
	      // 3、整合两个级别的request-handler信息,构造出完整的request-handler信息
         info = typeInfo.combine(info);  
      }  
      String prefix = getPathPrefix(handlerType);  
      if (prefix != null) {  
         info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);  
      }  
   }  
   return info;  
}

构造request地址信息很简单,只是从@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();  
}

在整合request地址信息过程中,会分别调用各个属性的整合规则进行整合:

public RequestMappingInfo combine(RequestMappingInfo other) {  
   String name = combineNames(other);  
  
   PathPatternsRequestCondition pathPatterns =  
         (this.pathPatternsCondition != null && other.pathPatternsCondition != null ?  
               this.pathPatternsCondition.combine(other.pathPatternsCondition) : null);  
  
   PatternsRequestCondition patterns =  
         (this.patternsCondition != null && other.patternsCondition != null ?  
               this.patternsCondition.combine(other.patternsCondition) : null);  
  
   RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);  
   ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);  
   HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);  
   ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);  
   ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);  
   RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);  
  
   return new RequestMappingInfo(name, pathPatterns, patterns,  
         methods, params, headers, consumes, produces, custom, this.options);  
}

不同的属性有不同的整合规则,比如对于methodsparamsheaders会取并集,而对于consumesproduces方法级别优先。

介绍完request地址信息的构造过程,我们回到AbstractHandlerMethodMappingdetectHandlerMethods方法中。此时,我们得到了Method-RequestMappingInfo映射信息。

接下来,会遍历这个映射,筛选出实际可执行的方法(即非私有的、非静态的和非超类的)。

最终,将可执行的方法对应的request-handler信息缓存起来。核心代码位于AbstractHandlerMethodMapping.MappingRegistry内部类的register()方法:

public void register(T mapping, Object handler, Method method) {  
   this.readWriteLock.writeLock().lock();  
   try {  
	   // 1、创建HandlerMethod对象,即handler
      HandlerMethod handlerMethod = createHandlerMethod(handler, method);  
      // 2、校验该request地址信息是否已经存在
      validateMethodMapping(handlerMethod, mapping);  
		// 3、缓存path-RequestMappingInfo映射
      Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);  
      for (String path : directPaths) {  
         this.pathLookup.add(path, mapping);  
      }  
		// 4、缓存name-RequestMappingInfo映射
      String name = null;  
      if (getNamingStrategy() != null) {  
         name = getNamingStrategy().getName(handlerMethod, mapping);  
         addMappingName(name, handlerMethod);  
      }  
		// 5、缓存CORS配置信息
      CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);  
      if (corsConfig != null) {  
         corsConfig.validateAllowCredentials();  
         this.corsLookup.put(handlerMethod, corsConfig);  
      }  
		// 6、缓存RequestMappingInfo-MappingRegistration信息
      this.registry.put(mapping,  
            new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));  
   }  
   finally {  
      this.readWriteLock.writeLock().unlock();  
   }  
}

需要注意的是,在这个过程中还会缓存跨域配置信息,主要是@CrossOrigin注解方式的跨域配置信息。
RequestMappingHandlerMappinginitCorsConfiguration()方法中,会获取类级别和方法级别的@CrossOrigin信息,构造出完整的跨域配置信息:

protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {  
   HandlerMethod handlerMethod = createHandlerMethod(handler, method);  
   Class<?> beanType = handlerMethod.getBeanType();  
   // 1、获取类级别的@CrossOrigin信息
   CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);  
   // 2、获取方法级别的@CrossOrigin信息
   CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);  
  
   if (typeAnnotation == null && methodAnnotation == null) {  
      return null;  
   }  
	// 3、整合两个级别的@CrossOrigin信息
   CorsConfiguration config = new CorsConfiguration();  
   updateCorsConfig(config, typeAnnotation);  
   updateCorsConfig(config, methodAnnotation);  
  
   if (CollectionUtils.isEmpty(config.getAllowedMethods())) {  
      for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {  
         config.addAllowedMethod(allowedMethod.name());  
      }  
   }  
   return config.applyPermitDefaultValues();  
}

在整合@CrossOrigin信息过程中,有三种情况:

  1. 对于originsoriginPatternsallowedHeadersexposedHeadersmethods等列表属性,会获取全部。
  2. 对于allowCredentials,会优先获取方法级别的配置。
  3. 对于maxAge,会获取最大值。

至此,我们走完了RequestMappingHandlerMapping中请求地址映射的初始化流程。最后总结一下流程如下:

  1. 遍历容器中所有bean对象
  2. 如果bean的类对象含有@Controller@RequestMapping注解,进行下一步
  3. 遍历bean的类对象的所有方法,根据方法的@RequestMapping注解,构造RequestMappingInfo对象
  4. 遍历Method-RequestMappingInfo映射,过滤出可执行方法
  5. 缓存各种request-handler映射信息,同时会缓存@CrossOrigin的跨域配置信息

此时,我们可以充分理解到,request-handler请求地址映射信息中requesthandler的含义:

  • request:主要是@RequestMapping中含有的各个属性的信息
  • handler:标注@RequestMapping的方法
12-13 23:26