springmvc揭秘参数解析

参数解析

说到参数解析,springmvc中处理参数的是HandlerMethodArgumentResolver接口

public interface HandlerMethodArgumentResolver {

   // 判断是否支持该类型参数
   boolean supportsParameter(MethodParameter parameter);

   // 进行参数解析
   Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
         NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}

其有一个抽象实现类AbstractNamedValueMethodArgumentResolver,有很多有用的子类

  • MapMethodProcessor

    这个用来处理 Map/ModelMap 类型的参数,解析完成后返回 model。

  • PathVariableMethodArgumentResolver

这个用来处理使用了 @PathVariable 注解并且参数类型不为 Map 的参数,参数类型为 Map 则使用 PathVariableMapMethodArgumentResolver 来处理。

  • PathVariableMapMethodArgumentResolver

见上。

  • ErrorsMethodArgumentResolver

这个用来处理 Error 参数,例如我们做参数校验时的 BindingResult。

  • AbstractNamedValueMethodArgumentResolver

    这个用来处理 key/value 类型的参数,如请求头参数、使用了 @PathVariable 注解的参数以及 Cookie 等。

  • RequestHeaderMethodArgumentResolver

这个用来处理使用了 @RequestHeader 注解,并且参数类型不是 Map 的参数(参数类型是 Map 的使用 RequestHeaderMapMethodArgumentResolver)。

  • RequestHeaderMapMethodArgumentResolver

见上。

  • RequestAttributeMethodArgumentResolver

这个用来处理使用了 @RequestAttribute 注解的参数。

  • RequestParamMethodArgumentResolver

这个功能就比较广了。使用了 @RequestParam 注解的参数、文件上传的类型 MultipartFile、或者一些没有使用任何注解的基本类型(Long、Integer)以及 String 等,都使用该参数解析器处理。需要注意的是,如果 @RequestParam 注解的参数类型是 Map,则该注解必须有 name 值,否则解析将由 RequestParamMapMethodArgumentResolver 完成。

  • RequestParamMapMethodArgumentResolver

见上。

  • AbstractCookieValueMethodArgumentResolver

这个是一个父类,处理使用了 @CookieValue 注解的参数。

  • ServletCookieValueMethodArgumentResolver

这个处理使用了 @CookieValue 注解的参数。

  • MatrixVariableMethodArgumentResolver

这个处理使用了 @MatrixVariable 注解并且参数类型不是 Map 的参数,如果参数类型是 Map,则使用 MatrixVariableMapMethodArgumentResolver 来处理。

  • MatrixVariableMapMethodArgumentResolver

见上。

  • SessionAttributeMethodArgumentResolver

这个用来处理使用了 @SessionAttribute 注解的参数。

  • ExpressionValueMethodArgumentResolver

这个用来处理使用了 @Value 注解的参数。

  • ServletResponseMethodArgumentResolver

这个用来处理 ServletResponse、OutputStream 以及 Writer 类型的参数。

  • ModelMethodProcessor

这个用来处理 Model 类型参数,并返回 model。

  • ModelAttributeMethodProcessor

这个用来处理使用了 @ModelAttribute 注解的参数。

  • SessionStatusMethodArgumentResolver

这个用来处理 SessionStatus 类型的参数。

  • PrincipalMethodArgumentResolver

这个用来处理 Principal 类型参数

  • AbstractMessageConverterMethodArgumentResolver

这是一个父类,当使用 HttpMessageConverter 解析 requestbody 类型参数时,相关的处理类都会继承自它。

  • RequestPartMethodArgumentResolver

这个用来处理使用了 @RequestPart 注解、MultipartFile 以及 Part 类型的参数。

  • RequestResponseBodyMethodProcessor

这个用来处理添加了 @RequestBody 注解的参数。

  • HttpEntityMethodProcessor

这个用来处理 HttpEntity 和 RequestEntity 类型的参数。

  • ServletWebArgumentResolverAdapter

这个给父类提供 request。

  • UriComponentsBuilderMethodArgumentResolver

这个用来处理 UriComponentsBuilder 类型的参数。

  • ServletRequestMethodArgumentResolver

这个用来处理 WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId 类型的参数。

  • HandlerMethodArgumentResolverComposite

这个看名字就知道是一个组合解析器,它是一个代理,具体代理其他干活的那些参数解析器。

  • RedirectAttributesMethodArgumentResolver

这个用来处理 RedirectAttributes 类型的参数

这些解析器是在执行

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

执行对应的Controller方法前来进行参数解析时调用的org.springframework.web.method.support.InvocableHandlerMethod#getMethodArgumentValues

if (this.argumentResolvers.supportsParameter(parameter)) {
   try {
      args[i] = this.argumentResolvers.resolveArgument(
            parameter, mavContainer, request, this.dataBinderFactory);
      continue;
   }
   catch (Exception ex) {
      if (logger.isDebugEnabled()) {
         logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
      }
      throw ex;
   }
}

RequestResponseBodyMethodProcessor调用消息解析器

我之前只知道解析@RequestBody需要使用消息解析器HttpMessageConverter,但是没有深究是从哪调用的。突然看到消息解析我才知道原来是RequestResponseBodyMethodProcessor调用的。org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#resolveArgument来进行参数解析

遍历消息解析器找到可以进行解析该类型的消息解析器

for (HttpMessageConverter<?> converter : this.messageConverters) {

对于简单的参数会以简单的转换器进行转换,而这些简单的转换器是Spring MVC自身已经提供了的,但是如果是转换HTTP请求体,会调用HttpMessageConverter接口的方法对请求体的信息进行转换

public interface HttpMessageConverter<T> {

   
  // 是否可读,clazz为java类型,mediaType为HTTP请求类型
   boolean canRead(Class<?> clazz, MediaType mediaType);

   
  // 判断clazz类型是否能够转换为mediaType媒体类型,其中clazz为java类型,mediaType为HTTP响应类型
   boolean canWrite(Class<?> clazz, MediaType mediaType);

   
  // 可支持的媒体类型列表
   List<MediaType> getSupportedMediaTypes();

   
  // 当canRead验证通过后,读入HTTP请求信息
   T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
         throws IOException, HttpMessageNotReadableException;

   
  // 单canWrite方法验证通过后,写入响应
   void write(T t, MediaType contentType, HttpOutputMessage outputMessage)
         throws IOException, HttpMessageNotWritableException;

}

HttpMessageConverter接口是将HTTP请求体转换为对应的java对象,对于HTTP参数和其他内容需要使用参数转换规则

参数转换

Spring MVC中,是通过WebDataBinder机制来获取参数的,它的主要作用是解析HTTP请求的上下文,然后在控制器的调用之前转换参数并且提供验证的功能,为调用控制器方法做准备。处理器会从HTTP请求中读取数据,然后通过三种接口进行各类参数转换(Converter、Formatter、GenericConverter)。

Converter接口

Converter接口是一个普通的转换器

public interface Converter<S, T> {

 
 T convert(S source);

}

可以将某个类型转换为另一个类型

Formatter接口

Formatter接口是一个格式化转换器,如将日期字符串格式化

public interface Formatter<T> extends Printer<T>, Parser<T> {

}
GenericConverter接口

GenericConverter接口是将HTTP参数转换为数组

public interface GenericConverter {

   
   Set<ConvertiblePair> getConvertibleTypes();

   
   Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);


   /**
    * Holder for a source-to-target class pair.
    */
   final class ConvertiblePair {

      private final Class<?> sourceType;

      private final Class<?> targetType;

      /**
       * Create a new source-to-target pair.
       * @param sourceType the source type
       * @param targetType the target type
       */
      public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
         Assert.notNull(sourceType, "Source type must not be null");
         Assert.notNull(targetType, "Target type must not be null");
         this.sourceType = sourceType;
         this.targetType = targetType;
      }

      public Class<?> getSourceType() {
         return this.sourceType;
      }

      public Class<?> getTargetType() {
         return this.targetType;
      }

      @Override
      public boolean equals(Object other) {
         if (this == other) {
            return true;
         }
         if (other == null || other.getClass() != ConvertiblePair.class) {
            return false;
         }
         ConvertiblePair otherPair = (ConvertiblePair) other;
         return (this.sourceType == otherPair.sourceType && this.targetType == otherPair.targetType);
      }

      @Override
      public int hashCode() {
         return (this.sourceType.hashCode() * 31 + this.targetType.hashCode());
      }

      @Override
      public String toString() {
         return (this.sourceType.getName() + " -> " + this.targetType.getName());
      }
   }

}
ConversionService接口
public interface ConversionService {

   boolean canConvert(Class<?> sourceType, Class<?> targetType);

   boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

   <T> T convert(Object source, Class<T> targetType);

   Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);

}

数据验证

Validator接口用于数据验证

public interface Validator {

  // 判断当前验证器是否支持该Class类型的验证
   boolean supports(Class<?> clazz);

   
  // 如果supports返回true,则执行该方法验证逻辑
   void validate(Object target, Errors errors);

}

WebDataBinder还可以进行验证,使用@InitBinder注解可以允许在进入控制器方法之前修改WebDataBinder机制,可以来设置验证器

通过WebDataBinder#setValidator来添加验证器

https://zhhll.icu/2023/框架/springmvc/底层剖析/6.参数解析/

本文由mdnice多平台发布

相关推荐
测开小菜鸟1 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity2 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天2 小时前
java的threadlocal为何内存泄漏
java
caridle3 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^3 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋33 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花3 小时前
【JAVA基础】Java集合基础
java·开发语言·windows
小松学前端3 小时前
第六章 7.0 LinkList
java·开发语言·网络
Wx-bishekaifayuan3 小时前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
customer083 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源