gpt4 book ai didi

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

转载 作者:我是一只小鸟 更新时间:2022-12-14 06:31:12 44 4
gpt4 key购买 nike

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

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

首先,我们简单来看一下 RequestMappingHandlerMapping 的类图:

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

                        
                          public void afterPropertiesSet() {  
   initHandlerMethods();  
}

                        
                      

在 AbstractHandlerMethodMapping 的 initHandlerMethods 方法中,会遍历容器中所有 bean 进行处理:

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

                        
                      

在 AbstractHandlerMethodMapping 的 processCandidateBean 方法中,会对 bean 进行筛选。如果该 bean 的类对象中包含 @Controller 或 RequestMapping 注解,会进一步遍历该类对象的各个方法:

                        
                          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);  
   }  
}

                        
                      

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

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

                        
                      

在 AbstractHandlerMethodMapping 的 detectHandlerMethods 方法中,会构造并缓存 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);  
      });  
   }  
}

                        
                      

在 MethodIntrospector 的 selectMethods() 方法中,会遍历类对象各个方法,调用 RequestMappingHandlerMapping 的 getMappingForMethod() 方法,构造 request 地址信息:

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

MethodIntrospector 的 selectMethods() 方法会将所有 request 地址信息不为 null 的 Method - RequestMappingInfo 映射返回.

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

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

RequestMappingHandlerMapping 的 getMappingForMethod() 方法源码如下:

                        
                          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);  
}

                        
                      

不同的属性有不同的整合规则,比如对于 methods 、 params 和 headers 会取并集,而对于 consumes 和 produces 方法级别优先.

介绍完 request 地址信息的构造过程,我们回到 AbstractHandlerMethodMapping 的 detectHandlerMethods 方法中。此时,我们得到了 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 注解方式的跨域配置信息。 在 RequestMappingHandlerMapping 的 initCorsConfiguration() 方法中,会获取类级别和方法级别的 @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. 对于 origins originPatterns allowedHeaders exposedHeaders methods 等列表属性,会获取全部。
  2. 对于 allowCredentials ,会优先获取方法级别的配置。
  3. 对于 maxAge ,会获取最大值。

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

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

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

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

最后此篇关于RequestMappingHandlerMapping请求地址映射的初始化流程!的文章就讲到这里了,如果你想了解更多关于RequestMappingHandlerMapping请求地址映射的初始化流程!的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

44 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com