SpringBoot项目中拦截器获取Body参数的问题

本文探讨了在SpringBoot项目中,由于HttpServletRequest的输入流只能读取一次导致的拦截器获取Body参数问题。解释了ServletInputStream不支持reset方法的原因,并提出通过HttpServletRequestWrapper和Filter来解决该问题。详细介绍了实现过程,包括自定义RequestWrapper、过滤器的设置以及测试Controller的编写。在实践中,注意到判断JSON数据时需考虑空格情况。
摘要由CSDN通过智能技术生成

系列文章目录


文章目录


前言

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。
在这里插入图片描述


首先我们要知道一个问题:

HttpServletRequest的输入流只能读取一次,如果你在拦截器读取了Body的参数,那么在Controller再次读取时,会直接报错,原因如下:

我们先来看看为什么HttpServletRequest的输入流只能读一次,当我们调用getInputStream()方法获取输入流时得到的是一个InputStream对象,而实际类型是ServletInputStream,它继承于InputStream。

InputStream的read()方法内部有一个postion,标志当前流被读取到的位置,每读取一次,该标志就会移动一次,如果读到最后,read()会返回-1,表示已经读取完了。如果想要重新读取则需要调用reset()方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读了。调用reset()方法的前提是已经重写了reset()方法,当然能否reset也是有条件的,它取决于markSupported()方法是否返回true。
在这里插入图片描述
InputStream默认不实现reset(),并且markSupported()默认也是返回false。

/**
 * Repositions this stream to the position at the time the
 * <code>mark</code> method was last called on this input stream.
 *
 * <p> The general contract of <code>reset</code> is:
 *
 * <ul>
 * <li> If the method <code>markSupported</code> returns
 * <code>true</code>, then:
 *
 *     <ul><li> If the method <code>mark</code> has not been called since
 *     the stream was created, or the number of bytes read from the stream
 *     since <code>mark</code> was last called is larger than the argument
 *     to <code>mark</code> at that last call, then an
 *     <code>IOException</code> might be thrown.
 *
 *     <li> If such an <code>IOException</code> is not thrown, then the
 *     stream is reset to a state such that all the bytes read since the
 *     most recent call to <code>mark</code> (or since the start of the
 *     file, if <code>mark</code> has not been called) will be resupplied
 *     to subsequent callers of the <code>read</code> method, followed by
 *     any bytes that otherwise would have been the next input data as of
 *     the time of the call to <code>reset</code>. </ul>
 *
 * <li> If the method <code>markSupported</code> returns
 * <code>false</code>, then:
 *
 *     <ul><li> The call to <code>reset</code> may throw an
 *     <code>IOException</code>.
 *
 *     <li> If an <code>IOException</code> is not thrown, then the stream
 *     is reset to a fixed state that depends on the particular type of the
 *     input stream and how it was created. The bytes that will be supplied
 *     to subsequent callers of the <code>read</code> method depend on the
 *     particular type of the input stream. </ul></ul>
 *
 * <p>The method <code>reset</code> for class <code>InputStream</code>
 * does nothing except throw an <code>IOException</code>.
 *
 * @exception  IOException  if this stream has not been marked or if the
 *               mark has been invalidated.
 * @see     java.io.InputStream#mark(int)
 * @see     java.io.IOException
 */
public synchronized void reset() throws IOException {
   
    throw new IOException("mark/reset not supported");
}

我们再来看看ServletInputStream,可以看到该类没有重写mark(),reset()以及markSupported()方法:

在这里插入图片描述
综上,InputStream默认不实现reset的相关方法,而ServletInputStream也没有重写reset的相关方法,这样就无法重复读取流,这就是我们从request对象中获取的输入流就只能读取一次的原因。

解决方案,使用HttpServletRequestWrapper + Filter解决输入流不能重复读取问题。

既然ServletInputStream不支持重新读写,那么为什么不把流读出来后用容器存储起来,后面就可以多次利用了。那么问题就来了,要如何存储这个流呢?

所幸JavaEE提供了一个HttpServletRequestWrapper类,从类名也可以知道它是一个http请求包装器,其基于装饰者模式实现了HttpServletRequest界面。该类并没有真正去实现HttpServletRequest的方法,而只是在方法内又去调用HttpServletRequest的方法,所以我们可以通过继承该类并实现想要重新定义的方法以达到包装原生HttpServletRequest对象的目的。

首先我们要定义一个容器,将输入流里面的数据存储到这个容器里,这个容器可以是数组或集合。然后我们重写getInputStream方法,每次都从这个容器里读数据,这样我们的输入流就可以读取任意次了。

package com.example.springboot.filter;
 
import lombok.extern.slf4j.Slf4j;
 
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
 
/**
 * 包装HttpServletRequest,目的是让其输入流可重复读
 **/
@Slf4j
public class RequestWrapper extends HttpServletRequestWrapper {
   
    /**
     * 存储body数据的容器
     */
    private final byte[] body;
 
    public RequestWrapper(HttpServletRequest request) throws IOException {
   
        super(request);
        // 将body数据存储起来
        String bodyStr = getBodyString(request);
        body = bodyStr.getBytes(Charset.defaultCharset());
    }
 
    /**
     * 获取请求Body
     */
    public String getBodyString(final ServletRequest request) {
   
        try
  • 29
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java毕设王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值