剖析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源码有所帮助~~

相关推荐
不务专业的程序员--阿飞几秒前
【SQL 如何解锁递归】
java·数据库·sql
嘵奇7 分钟前
Spring Boot拦截器详解:原理、实现与应用场景
java·spring boot·后端
八股文领域大手子8 分钟前
Java死锁排查:线上救火实战指南
java·开发语言·面试
jackson凌14 分钟前
【Java学习笔记】finalize方法
java·笔记·学习
fanTuanye17 分钟前
redis 缓存穿透,缓存击穿,缓存雪崩
java·redis·缓存
神秘的t35 分钟前
Spring Web MVC————入门(2)
java·spring·mvc
开开心心就好1 小时前
高效全能PDF工具,支持OCR识别
java·前端·python·pdf·ocr·maven·jetty
冷心笑看丽美人1 小时前
Spring MVC数据绑定和响应 你了解多少?
java·spring·mvc
XQ丶YTY1 小时前
大二java第一面小厂(挂)
java·开发语言·笔记·学习·面试
一零贰肆1 小时前
深入理解SpringBoot中的SpringCache缓存技术
java·springboot·springcache·缓存技术