java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java拦截器获取POST入参导致@RequestBody参数丢失

解决java拦截器获取POST入参导致@RequestBody参数丢失问题

作者:阿小冰

文章讲述了在Java开发中使用拦截器获取POST请求入参时,由于流关闭导致`@RequestBody`参数丢失的问题,并提出了一种解决方案,解决方案包括自定义方法、防止流丢失、过滤器和拦截器的合理组织和使用,最终确保了参数的正确传递

java拦截器获取POST入参导致@RequestBody参数丢失

在java开发中,拦截器的使用还是比较广泛的,例如:限制ip、接口校验、用户权限校验等等,大多首选都是使用拦截器,在正式请求接口之前先做一层校验,保证接口服务一定程度上的安全和可靠性

但是小冰在使用拦截器获取json入参的时候,遇到一个问题,那就是拦截器获取一般都是用流的方式获取,一般都是能获取到的,但是会引发另外一个问题:

拦截器相关校验走完之后,接口层的@RequestBody修饰的对象参数都会失效,并且调试之后释放会报错

org.springframework.http.converter.HttpMessageNotReadableException: I/O error while reading input message; nested exception is java.io.IOException: Stream closed

因为小冰用了流,并且存在流关闭,一次请求流关闭了就只能读取一次,所以到controller接口层就会报错,流的这个报错的意思就是:大哥,我都关门了,你还来找我干嘛,滚!

解决方案

于是找度娘,搜了一波,发现大多数处理方案都是相关报错,于是总结了一个小冰解决了问题的方案,如下:

首先上车的是:自定义方法小弟(用来获取参数-HttpRequestHelper)

/**
 * @description: 获取流并将想要的结果通过拼接返回
 **/
public class HttpRequestHelper {
    public static String getBodyString(HttpServletRequest request) throws IOException {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }
}

其次是:防流失大哥(防止流读取完之后就丢失了-MyRequestWrapper)

/**
 * @description: 防止流丢失
 **/
public class MyRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;

    public MyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        //返回参数字节数组
        body = HttpRequestHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }

}

然后是:过滤器大哥(将流中数据续传-HttpServletRequestFilter)

/**
 * @description: 使用过滤器处理流,将当前流放到一个新的request对象中
 **/
public class HttpServletRequestFilter implements Filter {
    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (request instanceof HttpServletRequest) {
            requestWrapper = new MyRequestWrapper((HttpServletRequest) request);
        }
        //获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
        // 在chain.doFiler方法中传递新的request对象
        if (requestWrapper == null) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }
}

最后是:拦截器大哥(获取流数据读取入参-只是代码片段)

//放到继承HandlerInterceptor的类下的preHandle方法中指定业务位置就行啦,这里读出来的string,
//这里我处理为一个json(就是入参对象的各个字段和字段值),方便操作
//构造函数的httpServletRequest参数是preHandle方法的request入参
JSONObject.parseObject(HttpRequestHelper.getBodyString(httpServletRequest));

然后运行,由于各位大哥就先走了,发现还是不对劲,好像丢了个司机,于是一回头一看,最牛的司机大哥还没上车,这是哪位大哥?答:过滤器Bean大哥

司机大哥:过滤器Bean大哥(将上面的过滤器大哥放在副驾上(在springboot的启动类中注入过滤器))

    @Bean
    public FilterRegistrationBean httpServletRequestReplacedRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        //把过滤器大哥安放到副驾上
        registration.setFilter(new HttpServletRequestFilter());
        registration.addUrlPatterns("/*");
        registration.addInitParameter("paramName", "paramValue");
        registration.setName("httpServletRequestFilter");
        registration.setOrder(1);
        return registration;
    }

然后运行,拦截获取json参数,因为有这几个大哥这么组织和坐镇,一开车,没问题,超速上高速,shua~

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文