剖析SpringMVC内部对于@ReqeustBody注解的解析


思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。

作者:毅航😜


前言

对于SpringMVC而言,@ReqesutBody注解可以说是在日常开发中使用频率最高的一个注解了。但你有了解过SpringMVC内部究竟是如何来解析@ReqesutBody注解吗?不了解也没关系,接下来我们就重点对SpringMVC@RequestBody的解析原理进行深入剖析。希望文章对你有所帮助中。

SpringMVC内部,对于@RequestBody注解的解析过程大致如下所示:

(此处笔者将对于@RequestBody解析过程先贴出来,以方便读者理解后续源码分析)

@ReqeustBody解析逻辑

接下来,笔者便手把手来逐行debug的方式来剖析@RequestBody解析的全过程。在开始分析之前希望你明确一点:即对于SpringMVC中的控制层而言,其相关参数解析会通过InvocableHandlerMethod # getMethodArgumentValues来完成!!!

本次请求的示例代码如下所示:

java 复制代码
@PostMapping("/duplicate")
public ResponseEntity getBookAndUserInfo(@RequestBody BookInfo bookInfo ) {
    BookInfoDto bookInfoDto = BookInfoDto.builder()
            .bookInfo(bookInfo)
            .userInfo(userInfo)
            .build();


    return new ResponseEntity(bookInfoDto, HttpStatus.OK);
}

正如我们之前所说的那样,控制层相关参数解析会通过InvocableHandlerMethod # getMethodArgumentValues来完成。因此我们的debug断点最先打在getMethodArgumentValues中。具体如下所示:

可以看到,在该方法中其首先获取请求路径对应方法中所包含的参数信息,然后逐个进行解析。随后,方法便会通过this.resolvers.resolveArgument执行到 HandlerMethodArgumentResolverComposite # resolveArgument内部。

通过上述Debug我们可以看到,在HandlerMethodArgumentResolverComposite # resolveArgument的内部,对于@ReqeustBody的解析,如果其包含有@ReqeustBody注解其首先会通过getArgumentResolver方法获取到一个名为RequestResponseBodyMethodProcessor的解析器。而这里判断是否有@ReqeustBody注解的思路也很简单,就是通过反射的方式判断方法上是否标有@ReqeustBody注解。具体细节在此便不过多赘述了。

我们接着往下Debug便会来到方法RequestResponseBodyMethodProcessor # resolveArgument内部,此处逻辑具体如下所示:

不难发现,在RequestResponseBodyMethodProcessor # resolveArgument的方法内部,其会调用一个名为readWithMessageConverters方法。

一直Debug便会来到RequestResponseBodyMethodProcessor父类AbstractMessageConverterMethodArgumentResolverreadWithMessageConverters中,该方法的具体逻辑如下:

java 复制代码
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType) {
      
   Object body = NO_VALUE;
    // .. 省略其他无关逻辑
   EmptyBodyCheckingHttpInputMessage message = null;
   try {
      // <1> 构建一个EmptyBodyCheckingHttpInputMessage对象
      message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
      // <2> 循环遍历SpringMVC中的HttpMessageConverter寻找到合适的处理器来完成解析
      for (HttpMessageConverter<?> converter : this.messageConverters) {
         Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
         GenericHttpMessageConverter<?> genericConverter =
               (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
         if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
               (targetClass != null && converter.canRead(targetClass, contentType))) {
            if (message.hasBody()) {
               HttpInputMessage msgToUse =
                     getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
               body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                     ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
               body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
            }
           
      }
   }
 
   // <3> 兜底操作,如果body经过处理器解析后,未被解析则返回null
   if (body == NO_VALUE) {
      if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
            (noContentType && !message.hasBody())) {
         return null;
      }
    // .. 省略其他无关逻辑
   return body;
}

虽然readWithMessageConverters而言内部逻辑众多,但其实readWithMessageConverters内部的逻辑其实关键主要有三处:

  1. <1>构建一个EmptyBodyCheckingHttpInputMessage对象
  2. <2> 循环遍历SpringMVC中的HttpMessageConverter寻找到合适的处理器来完成解析此处逻辑无非就是遍历SpringMVC容器在加载过程中所加载的默认的HttpMessageConverter来对内容进行解析处理。
  3. <3> 兜底操作,如果body经过处理器解析后,未被解析则返回null。此处属于兜底操作,不过多赘述!

为此我们重点分析两点,即之前提到的:

  1. <1>构建一个EmptyBodyCheckingHttpInputMessage对象。其内部逻辑如下所示:
java 复制代码
private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage {

   private final HttpHeaders headers;

   @Nullable
   private final InputStream body;

public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
   this.headers = inputMessage.getHeaders();
   InputStream inputStream = inputMessage.getBody();
   if (inputStream.markSupported()) {
      inputStream.mark(1);
      this.body = (inputStream.read() != -1 ? inputStream : null);
      inputStream.reset();
   }
   else {
      PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
      int b = pushbackInputStream.read();
      if (b == -1) {
         this.body = null;
      }
      else {
         this.body = pushbackInputStream;
         pushbackInputStream.unread(b);
      }
   }
}

于<1>中提及的EmptyBodyCheckingHttpInputMessage可能会有些陌生。简单来看,EmptyBodyCheckingHttpInputMessageSpring MVC 内部主要用于与消息转换器(MessageConverter)一起协同工作。消息转换器负责将请求体的内容转换为方法参数所需的类型,而在处理空请求体时,EmptyBodyCheckingHttpInputMessage 可以确保消息转换器能够正常工作,而不会因为空请求体而出现异常。

可以看到在EmptyBodyCheckingHttpInputMessage中其主要会读其首先会通过getBody()方法来获取请求中的请求体信息,进而根据请求中所会否有内容来执行不同逻辑,如果无法读取请求体中的内容则会将输入流body置为null

<2> 循环遍历SpringMVC中的HttpMessageConverter寻找到合适的处理器来完成解析。这部分逻辑如下所示:

java 复制代码
 // <2> 循环遍历SpringMVC中的HttpMessageConverter寻找到合适的处理器来完成解析
      for (HttpMessageConverter<?> converter : this.messageConverters) {
         Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
         GenericHttpMessageConverter<?> genericConverter =
               (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
         if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
               (targetClass != null && converter.canRead(targetClass, contentType))) {
            if (message.hasBody()) {
               HttpInputMessage msgToUse =
                     getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
               body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
                     ((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
               body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
            }
           
      }
   }

这块的逻辑就很简单了,就是遍历SpringMVC内部各种转换器来完成信息的映射处理。对于@ReqeustBody转换器的解析全过程可参考之前SpringMVC流程分析(九):从源码解释@ReqeustBody参数无法绑定的问题的分析~

总结

其实在SpringMVC内部其对于@ReqeustBody注解的解析本质就是对请求体中Json格式数据的封装转换,只不过其在解析过程中会牵扯到多个组件的共同联动,进而导致这部分代码看起来很复杂,而本文便抛砖引玉式的对@ReqeustBody注解的解析流程进行了分析,希望文章对你理解SpringMVC源码有所帮助~~

相关推荐
仓三1 分钟前
从 Prompt Engineering 到 Context Engineering:2026 年 Agent 性能提升的隐藏杠杆
java·prompt·context
许彰午10 分钟前
07-SqlBuilder六法
java·开发语言·低代码·架构
孙69034215 分钟前
Spring 注入多例 Bean
java·spring
Jul1en_16 分钟前
【Java 脚手架】封装通用工具类-3
java·开发语言·redis·缓存·ai·bootstrap·rabbitmq
那咋乎吧20 分钟前
线程上下文切换和用户态进入内核态 linux内核都发生了什么
后端
YIAN28 分钟前
NestJS 入门核心梳理:模块化架构、装饰器与依赖注入
后端·nestjs
唐青枫39 分钟前
别只会 malloc:Zig Allocator、所有权与内存生命周期实战
后端
边境悍匪1 小时前
springboot常用注解
java·spring boot·学习
风曳丷1 小时前
12|用 Attack Tree 和 Trace 固化证据
后端
江华森1 小时前
云原生从0到1:Kubernetes 工作负载实战——Deployment/Service/滚动更新/弹性伸缩
前端·后端