剖析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 小时前
AtomCode开发微信小程序《谁去呀》 全流程
前端·javascript·后端
GetcharZp1 小时前
Epic、暴雪都在用的 C++ 界面利器:Dear ImGui 零基础全景指南
后端
C+++Python2 小时前
详细介绍一下Java泛型的通配符
java·windows·python
pixcarp2 小时前
知识库系统的内容资产闭环怎么设计
服务器·数据库·后端·golang
红尘散仙3 小时前
别再手动录屏了:用 VHS 给终端应用生成会动的文档素材
后端·rust
JosieBook3 小时前
【数据库】时序预测能力的分级进化:TimechoAI如何让每一类用户都能精准预见未来
java·开发语言·数据库
一生了无挂3 小时前
Java处理JSON技巧教学(从基础到高阶实战全覆盖)
java·开发语言·json
李白的天不白4 小时前
使用 SmartAdmin 进行前后端开发
java·前端
swordbob4 小时前
Spring 单例 Bean 是线程安全的吗?
java·开发语言
2601_951643775 小时前
Python第一,Java跌出前三,C语言杀回来了
java·c语言·python·编程语言排行·技术趋势