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

相关推荐
qq_4419960512 分钟前
Mybatis官方生成器使用示例
java·mybatis
巨大八爪鱼18 分钟前
XP系统下用mod_jk 1.2.40整合apache2.2.16和tomcat 6.0.29,让apache可以同时访问php和jsp页面
java·tomcat·apache·mod_jk
码上一元2 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
计算机-秋大田2 小时前
基于微信小程序的养老院管理系统的设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
魔道不误砍柴功4 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
枫叶_v4 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
wclass-zhengge4 小时前
SpringCloud篇(配置中心 - Nacos)
java·spring·spring cloud
路在脚下@4 小时前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
黑马师兄4 小时前
SpringBoot
java·spring