RequestMappingHandlerMapping请求地址映射的初始化流程!

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

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

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

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的方法
发表评论

评论已关闭。

相关文章