Spring常见问题解决 - Required request body is missing

  • 前言
  • 一. 案例复现
  • 二. 原理分析
  • 三. 问题解决
    • 3.1 自定义适配器代替过滤器
    • 3.2 包装流并返回

前言

可以看下Spring常见问题解决 - @EnableWebMvc 导致自定义序列化器失效。

一. 案例复现

可以添加一个pom依赖:

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-io</artifactId><version>1.3.2</version>
</dependency>

1.我们自定义一个过滤器MyFilter

import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;/*** @author Zong0915* @date 2022/8/31 下午7:36*/
@Component
@WebFilter(urlPatterns = "/*", filterName = "myFilter")
public class MyFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {String requestBody = IOUtils.toString(request.getInputStream(), "utf-8");System.out.println("print request body in filter:" + requestBody);chain.doFilter(request, response);}
}

2.Controller类:

@RestController
public class MyController {@PostMapping("/hello")public User hello(@RequestBody User user){return user;}
}

3.访问对应的接口:

控制台输出如下:

这里有句话太长了,我再贴一个:

二. 原理分析

在前面的文章我讲到过关于转换器的一些问题,并且多次用一段代码来验证当前请求用的是什么转换器,代码如下AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters

@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {// ...Object body = NO_VALUE;EmptyBodyCheckingHttpInputMessage message;try {message = new EmptyBodyCheckingHttpInputMessage(inputMessage);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()) {// ..对结果的转换解析}else {//处理没有 body 情况,默认返回 nullbody = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);}break;}}}// ..return body;
}

我们得知,message使用EmptyBodyCheckingHttpInputMessage类型来进行包装,我们看下这个类的构造函数:

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);// 当前流是否被读取过,如果被读取过就是-1,此时this.body就赋值为nullint b = pushbackInputStream.read();if (b == -1) {this.body = null;}else {this.body = pushbackInputStream;pushbackInputStream.unread(b);}}
}

而我们在过滤器定义了这段代码:

String requestBody = IOUtils.toString(request.getInputStream(), "utf-8");

正式因为这个流被读取过了,导致在后续对请求体进行解析的时候int b = pushbackInputStream.read();发现该流的内容已经被读取完毕了,所以请求体是空。所以报出了这样的错误:

Required request body is missing

注意:

  • InputStream.read方法内部会记录position,用于记录当前流读取到的位置。若已读完,read方法会返回-1。因此不能重复读取。

那么我们如何解决这个问题?我们可以继续看下解析请求体的代码,有这么一段代码:

body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,(Class<? extends HttpMessageConverter<?>>) converter.getClass(),inputMessage, outputMessage);

当一个 Body 被解析出来后,会调用 getAdvice() 来获取 RequestResponseBodyAdviceChain;然后在这个 Chain 中,寻找合适的 Advice 并执行(即适配器)。做一些包装处理。那么我们可以基于这个特性去解决这个问题。

三. 问题解决

3.1 自定义适配器代替过滤器

方式一:自定义一个适配器MyRequestBodyAdviceAdapter来代替我们的过滤器工作。目的就是希望读取一下请求体。

@ControllerAdvice
public class MyRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return true;}@Overridepublic Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {System.out.println("MyRequestBodyAdviceAdapter-afterBodyRead: body: " + body);return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);}
}

不过这种方式有一点需要注意的是:

  • 这里拿到的bodyObject类型的Java对象,不再是InputStream流。
  • 因此可以避免请求体以InputStream的形式被读取两次。导致 Required request body is missing的异常。

结果如下:


3.2 包装流并返回

方式二:我们依旧使用过滤器,依旧读取一遍InputStream流。但是我们对齐进行包装,然后再返回。

我们自定义一个包装流对象BodyReaderWrapper

public class BodyReaderWrapper extends HttpServletRequestWrapper {//用于将流保存下来private byte[] requestBody;public BodyReaderWrapper(HttpServletRequest request) throws IOException {super(request);requestBody = StreamUtils.copyToByteArray(request.getInputStream());String requestBodyStr = IOUtils.toString(requestBody, "utf-8");System.out.println("print request body in filter:" + requestBodyStr);}@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);return new ServletInputStream() {@Overridepublic int read() throws IOException {return bais.read();}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}};}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}
}

注意:

  1. 因为我们定义了一个属性,用来保存流对象。
  2. 因此getInputStream()需要重写,读取包装类中存储的流对象。
  3. 那么随之,getReader()也需要重写。

过滤器做出更改:

@Component
@WebFilter(urlPatterns = "/*", filterName = "myFilter")
public class MyFilter implements Filter {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {// 将流对象包装一下,然后返回BodyReaderWrapper bodyReaderWrapper = new BodyReaderWrapper((HttpServletRequest) request);chain.doFilter(bodyReaderWrapper, response);}
}

结果如下:

Spring常见问题解决 - Required request body is missing相关推荐

  1. Java中Required request body is missing问题解决

    报Required request body is missing这个问题的原因是请求类型,引入的参数注解不对应 解决 检查Controller 接口接收的参数注解 post请求使用注解@Reques ...

  2. 解决SpringMvc框架中提示的“Required request body is missing”异常。

    之前没搞过Web后台,现在刚接手后台项目就遇到了这种异常问题,做次记录,避免更多的人跳坑. 瞎搞了两天才找到问题原因也是没谁了. 出现该"Required request body is m ...

  3. Spring常见问题解决 - AOP调用被拦截类的属性报NPE

    Spring常见问题解决 - AOP调用被拦截类的属性报NPE 一. 案例复现 二. 被拦截类的属性为何是null? 2.1 原理分析 2.2 解决 2.2.1 为何加一个 get 方法就可以避免NP ...

  4. 报错,Exception: Required request body is missing: public org.springframework.ui.ModelMap cn.yihuazt.co

    问题: 2021-04-29 15:06:16.795 ERROR 30460 --- [io-12000-exec-1] c.y.w.c.GlobalControllerExceptionHandl ...

  5. 异常Required request body is missing。

    前几天在调用接口时,请求的参数明明都已经填上去了,但就是报错,Required request body is missing.两种方法 上面报异常 (1)把**@RequestBody **去掉就好 ...

  6. Required request body is missing错误

    错误提示如下: org.springframework.http.converter.HttpMessageNotReadableException: Required request body is ...

  7. 解决:Required request body is missing

    报错信息:         WARN DefaultHandlerExceptionResolver:384 - Failed to read HTTP message:          org.s ...

  8. 关于加入@RequestBody后请求报错:Required request body is missing:

    关于加入@RequestBody后请求报错: Required request body is missing: 这个错误是由于Controller中加入了@RequestBody后却收不到指定请求体 ...

  9. 返回code400,报错Required request body is missing

    做登录的时候,使用postman可以获取返回值,但是使用Retrofit做的时候就报错,Debug一下,发现返回code为400,message为Required request body is mi ...

  10. Required request body is missing

    美好的一天,从解决BUG开始! org.springframework.http.converter.HttpMessageNotReadableException: Required request ...

最新文章

  1. [deviceone开发]-do_Dialog的基本使用示例
  2. Go赋值使用:类型{} 定位使用.
  3. com.esri.android,解决ArcGIS Android Could not find class 'com.esri.android.map.MapView'问题
  4. 线程间协作的两种方式:wait、notify、notifyAll和Condition
  5. php-5.2.6安装,php5.2.6安装openssl.o扩展,make时报错?
  6. 【python】内建异常类的层次
  7. oracle查看表空间和物理文件大小
  8. 报表人的福音!25个实用报表模板合集,适用多个业务场景
  9. jupyetr notebook添加anaconda虚拟环境内核(tensorflow+pytorch)
  10. eclipse 背景颜色
  11. 疫情下,嵌入式er该怎么进行职业规划,难点在哪?
  12. Python实现离线字典+听写单词(一):获取离线字典
  13. IEC 60601-2-33:2022 《医疗诊断用磁共振设备基本安全和基本性能的特殊要求》。
  14. SCCM 2016安装部署
  15. 勒索病毒威胁的解决方案
  16. 两条命令彻底修复动态链接库
  17. spin_lock到spin_lock_irqsave的使用
  18. python的argc与argv
  19. java基础国庆作业_国庆JAVA作业
  20. Python 怎么利用Python绘制二元高次隐函数的函数图像及其极值点——以某双核论文模型方程为例

热门文章

  1. Acm - 隔壁老王买酒问题
  2. 详解 python 的 切片
  3. 【金猿产品展】EasyTwin——国产自研数字孪生融合渲染引擎
  4. 线程main java中的异常怎么解决_线程“ main”中的异常java.lang.NoClassDefFoundError:...
  5. 智能系统概论——初识百度AI平台
  6. 胡昊—第8次作业--继承
  7. JavaFX: Alert 弹窗
  8. 腾讯让企业微信连接微信,这是针对钉钉的精准打击吗?
  9. Java中将List分组到Map中算法(可用于android联系人拼音分组)
  10. 2022-2027年中国喷涂机器人行业市场调研及未来发展趋势预测报告