HandlerMethodArgumentResolver(三):基于HttpMessageConverter消息转换器的参数解析器【享学Spring MVC】(上)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: HandlerMethodArgumentResolver(三):基于HttpMessageConverter消息转换器的参数解析器【享学Spring MVC】(上)

前言


通过 前面两篇文章 的介绍,相信你对HandlerMethodArgumentResolver了解已经很深刻了。但是你或许和我一样还有一种感觉,似乎还缺点什么:

我们使用非常频繁的@RequestBody是怎么封装请求体的呢???这块使用非常广泛的地方却还木有讲解到,因为它的处理方式和前面的不太一样,因此单摘出来到本文进行详细描述。


第四类:基于ContentType消息转换器类型




利用HttpMessageConverter将输入流转换成对应的参数


这类参数解析器的基类是AbstractMessageConverterMethodArgumentResolver:



// @since 3.1
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
  // 默认支持的方法(没有Deleted方法)
  // httpMethod为null 或者方法不属于这集中 或者没有contendType且没有body 那就返回null
  // 也就是说如果是Deleted请求,即使body里有值也是返回null的。(因为它不是SUPPORTED_METHODS )
  private static final Set<HttpMethod> SUPPORTED_METHODS = EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH);
  private static final Object NO_VALUE = new Object();
  protected final List<HttpMessageConverter<?>> messageConverters;
  protected final List<MediaType> allSupportedMediaTypes;
  // 和RequestBodyAdvice和ResponseBodyAdvice有关的
  private final RequestResponseBodyAdviceChain advice;
  // 构造函数里指定HttpMessageConverter
  // 此一个参数的构造函数木人调用
  public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
    this(converters, null);
  }
  // @since 4.2
  public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters, @Nullable List<Object> requestResponseBodyAdvice) {
    Assert.notEmpty(converters, "'messageConverters' must not be empty");
    this.messageConverters = converters;
    // 它会把所有的消息转换器里支持的MediaType都全部拿出来汇聚起来~
    this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
    this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
  }
  // 提供一个defualt方法访问
  RequestResponseBodyAdviceChain getAdvice() {
    return this.advice;
  }
  // 子类RequestResponseBodyMethodProcessor有复写此方法
  @Nullable
  protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
    HttpInputMessage inputMessage = createInputMessage(webRequest);
    return readWithMessageConverters(inputMessage, parameter, paramType);
  }
  ...
}


说明:此抽象类并没有实现resolveArgument()这个接口方法,而只是提供了一些protected方法,作为工具方法给子类调用,比如最为重要的这个方法:readWithMessageConverters()就是利用消息转换器解析HttpInputMessage的核心。


关于此抽象类的描述,可以看 这里,HttpMessageConverter匹配规则


它的继承树如下:

image.png


RequestPartMethodArgumentResolver

它用于解析参数被@RequestPart修饰,或者参数类型是MultipartFile | Servlet 3.0提供的javax.servlet.http.Part类型(并且没有被@RequestParam修饰),数据通过 HttpServletRequest获取


当属性被标注为@RequestPart的话,那就会经过HttpMessageConverter结合Content-Type来解析,这个效果特别像@RequestBody的处理方式~

// @since 3.1
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPart {
  @AliasFor("name")
  String value() default "";
  @AliasFor("value")
  String name() default "";
  boolean required() default true;
}


// @since 3.1
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {
  // 标注了@RequestPart注解的
  // 没有标注@RequestPart并且也没有标注@RequestParam,但是是Multipart类型的也会处理
  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    if (parameter.hasParameterAnnotation(RequestPart.class)) {
      return true;
    } else {
      if (parameter.hasParameterAnnotation(RequestParam.class)) {
        return false;
      }
      return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
    }
  }
  @Override
  @Nullable
  public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
      NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
    Assert.state(servletRequest != null, "No HttpServletRequest");
    RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
    boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
    // 如果注解没有指定,就取形参名
    String name = getPartName(parameter, requestPart);
    parameter = parameter.nestedIfOptional();
    Object arg = null;
    // resolveMultipartArgument这个方法只处理:
    // MultipartFile类型以及对应的数组/集合类型
    // Part类型以及对应的数组集合类型
    // 若形参类型不是以上类型,返回UNRESOLVABLE(空对象)
    // 最终返回StandardMultipartHttpServletRequest/request.getParts()[0]等~
    Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
    if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
      arg = mpArg; // 是part类型,那就直接赋值吧
    } else { // 其它类型
      ...
    }
    ...
  }
}

此处理器用于解析@RequestPart参数类型,它和多部分文件上传有关。关于Spring MVC中的文件上传,此处就不便展开了。后面有个专题专门讲解Spring MVC中的上传、下载~


AbstractMessageConverterMethodProcessor(重点)

命名为Processor说明它既能处理入参,也能处理返回值,当然本文的关注点是方法入参(和HttpMessageConverter相关)。


请求body体一般是一段字符串/字节流,查询参数可以看做URL的一部分,这两个是位于请求报文的不同地方。


表单参数可以按照一定格式放在请求体中,也可以放在url上作为查询参数。


响应body体则是response返回的具体内容,对于一个普通的html页面,body里面就是页面的源代码。对于HttpMessage响应体里可能就是个json串(但无强制要求)。


响应体一般都会结合Content-Type一起使用,告诉客户端只有知道这个头了才知道如何渲染。


AbstractMessageConverterMethodProcessor源码稍显复杂,它和Http协议、内容协商有很大的关联:


// @since 3.1
public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler {
  // 默认情况下:文件们后缀是这些就不弹窗下载
  private static final Set<String> WHITELISTED_EXTENSIONS = new HashSet<>(Arrays.asList("txt", "text", "yml", "properties", "csv",
      "json", "xml", "atom", "rss", "png", "jpe", "jpeg", "jpg", "gif", "wbmp", "bmp"));
  private static final Set<String> WHITELISTED_MEDIA_BASE_TYPES = new HashSet<>(Arrays.asList("audio", "image", "video"));
  private static final List<MediaType> ALL_APPLICATION_MEDIA_TYPES = Arrays.asList(MediaType.ALL, new MediaType("application"));
  private static final Type RESOURCE_REGION_LIST_TYPE = new ParameterizedTypeReference<List<ResourceRegion>>() { }.getType();
  // 用于给URL解码 decodingUrlPathHelper.decodeRequestString(servletRequest, filename);
  private static final UrlPathHelper decodingUrlPathHelper = new UrlPathHelper();
  // rawUrlPathHelper.getOriginatingRequestUri(servletRequest);
  private static final UrlPathHelper rawUrlPathHelper = new UrlPathHelper();
  static {
    rawUrlPathHelper.setRemoveSemicolonContent(false);
    rawUrlPathHelper.setUrlDecode(false);
  }
  // 内容协商管理器
  private final ContentNegotiationManager contentNegotiationManager;
  // 扩展名的内容协商策略
  private final PathExtensionContentNegotiationStrategy pathStrategy;
  private final Set<String> safeExtensions = new HashSet<>();
  protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters) {
    this(converters, null, null);
  }
  // 可以指定内容协商管理器ContentNegotiationManager 
  protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager contentNegotiationManager) {
    this(converters, contentNegotiationManager, null);
  }
  // 这个构造器才是重点
  protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {
    super(converters, requestResponseBodyAdvice);
    // 可以看到:默认情况下会直接new一个
    this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
    // 若管理器里有就用管理器里的,否则new PathExtensionContentNegotiationStrategy()
    this.pathStrategy = initPathStrategy(this.contentNegotiationManager);
    // 用safeExtensions装上内容协商所支持的所有后缀
    // 并且把后缀白名单也加上去(表示是默认支持的后缀)
    this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
    this.safeExtensions.addAll(WHITELISTED_EXTENSIONS);
  }
  // ServletServerHttpResponse是对HttpServletResponse的包装,主要是对响应头进行处理
  // 主要是处理:setContentType、setCharacterEncoding等等
  // 所以子类若要写数据,就调用此方法来向输出流里写吧~~~
  protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
    HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
    Assert.state(response != null, "No HttpServletResponse");
    return new ServletServerHttpResponse(response);
  }
  // 注意:createInputMessage()方法是父类提供的,对HttpServletRequest的包装
  // 主要处理了:getURI()、getHeaders()等方法
  // getHeaders()方法主要是处理了:getContentType()...
  protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    writeWithMessageConverters(value, returnType, inputMessage, outputMessage);
  }
  // 这个方法省略
  // 这个方法是消息处理的核心之核心:处理了contentType、消息转换、内容协商、下载等等
  // 注意:此处并且还会执行RequestResponseBodyAdviceChain,进行前后拦截
  protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
      ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
      throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { ... }
}
目录
打赏
0
0
0
0
37
分享
相关文章
1天消化完Spring全家桶文档!DevDocs:一键深度解析开发文档,自动发现子URL并建立图谱
DevDocs是一款基于智能爬虫技术的开源工具,支持1-5层深度网站结构解析,能将技术文档处理时间从数周缩短至几小时,并提供Markdown/JSON格式输出与AI工具无缝集成。
78 1
1天消化完Spring全家桶文档!DevDocs:一键深度解析开发文档,自动发现子URL并建立图谱
|
14天前
|
深入解析 Spring Security 配置中的 CSRF 启用与 requestMatchers 报错问题
本文深入解析了Spring Security配置中CSRF启用与`requestMatchers`报错的常见问题。针对CSRF,指出默认已启用,无需调用`enable()`,只需移除`disable()`即可恢复。对于`requestMatchers`多路径匹配报错,分析了Spring Security 6.x中方法签名的变化,并提供了三种解决方案:分次调用、自定义匹配器及降级使用`antMatchers()`。最后提醒开发者关注版本兼容性,确保升级平稳过渡。
73 2
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
77 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
Spring Boot 便利店销售系统项目分包设计解析
本文深入解析了基于Spring Boot的便利店销售系统分包设计,通过清晰的分层架构(表现层、业务逻辑层、数据访问层等)和模块化设计,提升了代码的可维护性、复用性和扩展性。具体分包结构包括`controller`、`service`、`repository`、`entity`、`dto`、`config`和`util`等模块,职责分明,便于团队协作与功能迭代。该设计为复杂企业级应用开发提供了实践参考。
48 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
101 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
74 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
78 0
深入解析 @Transactional——Spring 事务管理的核心
本文深入解析了 Spring Boot 中 `@Transactional` 的工作机制、常见陷阱及最佳实践。作为事务管理的核心注解,`@Transactional` 确保数据库操作的原子性,避免数据不一致问题。文章通过示例讲解了其基本用法、默认回滚规则(仅未捕获的运行时异常触发回滚)、因 `try-catch` 或方法访问修饰符不当导致失效的情况,以及数据库引擎对事务的支持要求。最后总结了使用 `@Transactional` 的五大最佳实践,帮助开发者规避常见问题,提升项目稳定性与可靠性。
133 11
深入解析HTTP请求方法:Spring Boot实战与最佳实践
这篇博客结合了HTTP规范、Spring Boot实现和实际工程经验,通过代码示例、对比表格和架构图等方式,系统性地讲解了不同HTTP方法的应用场景和最佳实践。
76 5
Spring Security: 深入解析 AuthenticationSuccessHandler
本文深入解析了 Spring Security 中的 `AuthenticationSuccessHandler` 接口,它用于处理用户认证成功后的逻辑。通过实现该接口,开发者可自定义页面跳转、日志记录等功能。文章详细讲解了接口方法参数及使用场景,并提供了一个根据用户角色动态跳转页面的示例。结合 Spring Security 配置,展示了如何注册自定义的成功处理器,帮助开发者灵活应对认证后的多样化需求。
47 2

热门文章

最新文章

推荐镜像

更多
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等