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

 更新时间:2024年11月12日 09:22:32   作者:阿小冰  
文章讲述了在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接口层就会报错,流的这个报错的意思就是:大哥,我都关门了,你还来找我干嘛,滚!

解决方案

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

  • 思路:不管流关没关闭,要把流中的参数,延伸到后面的接口去用就可以了
  • 需要的组织:过滤器大哥、拦截器大哥、防流失大哥、过滤器Bean大哥还有一个自定义方法小弟
  • 立马上车出发:

首先上车的是:自定义方法小弟(用来获取参数-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~

总结

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

相关文章

  • Spring boot项目整合WebSocket方法

    Spring boot项目整合WebSocket方法

    这篇文章主要介绍了WebSocket使用Spring boot整合方法,需要继承webSocketHandler类,重写几个方法就可以了,具体实例代码跟随小编一起看看吧
    2021-09-09
  • 浅谈Java内存区域划分和内存分配策略

    浅谈Java内存区域划分和内存分配策略

    这篇文章主要介绍了浅谈Java内存区域划分和内存分配策略,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05
  • SpringMVC实现文件的上传和下载实例代码

    SpringMVC实现文件的上传和下载实例代码

    本篇文章主要介绍了SpringMVC实现文件的上传和下载实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • Java 中的io模型详解

    Java 中的io模型详解

    这篇文章主要介绍了Java 中io模型的相关资料,帮助大家更好的理解和学习使用Java,感兴趣的朋友可以了解下
    2021-04-04
  • 解析Java实现设计模式六大原则之里氏替换原则

    解析Java实现设计模式六大原则之里氏替换原则

    里氏替换原则是用来帮助我们在继承关系中进行父子类的设计。它阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。它是继承复用的基础,反映了基类与子类之间的关系,是对开闭原则的补充,对实现抽象化具体步骤的规范
    2021-06-06
  • SpringMVC中ModelAndView用法小结

    SpringMVC中ModelAndView用法小结

    本文主要介绍了SpringMVC中ModelAndView用法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-12-12
  • 深入浅出讲解Spring框架中AOP及动态代理的应用

    深入浅出讲解Spring框架中AOP及动态代理的应用

    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术
    2022-03-03
  • SpringBoot项目集成FTP的方法步骤

    SpringBoot项目集成FTP的方法步骤

    本文主要介绍了SpringBoot项目集成FTP的方法步骤,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • @Scheduled中的参数及Cron时间表达式用法详解

    @Scheduled中的参数及Cron时间表达式用法详解

    在SpringBoot中使用@Scheduled注解设置定时任务,可设置fixedDelay、fixedRate、initialDelay和cron参数,首句概括,后续分条列出参数解释和Cron表达式格式,最后给出示例
    2026-05-05
  • Java如何将Excel数据导入到数据库

    Java如何将Excel数据导入到数据库

    这篇文章主要为大家详细介绍了Java将Excel数据导入到数据库的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10

最新评论