解决Java项目中request流只能获取一次的问题

 更新时间:2024年02月23日 11:11:59   作者:嗑嗑嗑瓜子的猫  
Java项目开发中可能存在以下几种情况,你需要在拦截器中统一拦截请求和你项目里可能需要搞一个统一的异常处理器,这两种情况是比较常见的,本文将给大家介绍如何解决Java项目中request流只能获取一次的问题,需要的朋友可以参考下

问题描述

Java项目开发中可能存在以下几种情况:

1、你需要在拦截器中统一拦截请求,拿到请求中的参数,来做统一的判断处理或者其他操作。

那问题就来了,由于request输入流的数据只能读取一次,所以你在拦截器中你读取了输入流的数据,当请求进入后边的Controller时,输入流中已经没有数据了,导致获取不到数据。

2、你项目里可能需要搞一个统一的异常处理器,然后想在异常处理器中把发生异常的接口地址,方法名,以及请求的参数记录到日志里或者直接发送给你配置的告警系统,比如发送给钉钉群通知。这种情况下,因为你前边controller已经获取过一次request输入流了,在后边的异常处理器里你还想再从request输入流中拿到请求参数等信息,所以也会出现request流只能读取一次的错误。

以上两种情况是开发中比较常见的,当然除此之外,别的场景下你可能也会遇到request流只能读取一次的错误,所以今天就来讲一下如果遇到这种情况该怎么解决。

产生原因

1、一个InputStream对象在被读取完成后,将无法被再次读取,始终返回-1;

2、InputStream并没有实现reset方法(可以重置首次读取的位置),无法实现重置操作;

解决方法

一、引入依赖

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.7</version>
</dependency>

二、自定义Wrapper类来包装HttpServletRequest

我们需要写一个自定义包装类,并继承HttpServletRequestWrapper

import org.apache.commons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
 
/**
 * @描述 包装HttpServletRequest
 *          MyServletRequestWrapper + RequestReplaceFilter 的作用是:
 *              解决异常处理器中拿post请求的json参数时,报request流只能读一次的错
 *              原因是 request.getReader() 和 request.getInputStream() 都是只能调用一次
 *              所以我这里要使用 HttpServletRequestWrapper 来实现自定义的 MyServletRequestWrapper包装类
 *              把request里的 body 保存在 MyServletRequestWrapper中, 并且重写 getInputStream()方法
 *              然后所有的request都在RequestReplaceFilter中被转换成了我自定义的HttpServletRequestWrapper
 *              然后获取 body时就都是调用 MyServletRequestWrapper中的 getBody()方法了
 * @创建人 caoju
 */
public class MyServletRequestWrapper extends HttpServletRequestWrapper {
 
    private final byte[] body;
 
    public MyServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = IOUtils.toByteArray(super.getInputStream());
    }
 
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
 
    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new RequestBodyCachingInputStream(body);
    }
 
    private class RequestBodyCachingInputStream extends ServletInputStream {
        private byte[] body;
        private int lastIndexRetrieved = -1;
        private ReadListener listener;
 
        public RequestBodyCachingInputStream(byte[] body) {
            this.body = body;
        }
 
        @Override
        public int read() throws IOException {
            if (isFinished()) {
                return -1;
            }
            int i = body[lastIndexRetrieved + 1];
            lastIndexRetrieved++;
            if (isFinished() && listener != null) {
                try {
                    listener.onAllDataRead();
                } catch (IOException e) {
                    listener.onError(e);
                    throw e;
                }
            }
            return i;
        }
 
        @Override
        public boolean isFinished() {
            return lastIndexRetrieved == body.length - 1;
        }
 
        @Override
        public boolean isReady() {
            return isFinished();
        }
 
        @Override
        public void setReadListener(ReadListener listener) {
            if (listener == null) {
                throw new IllegalArgumentException("listener cann not be null");
            }
            if (this.listener != null) {
                throw new IllegalArgumentException("listener has been set");
            }
            this.listener = listener;
            if (!isFinished()) {
                try {
                    listener.onAllDataRead();
                } catch (IOException e) {
                    listener.onError(e);
                }
            } else {
                try {
                    listener.onAllDataRead();
                } catch (IOException e) {
                    listener.onError(e);
                }
            }
        }
 
        @Override
        public int available() throws IOException {
            return body.length - lastIndexRetrieved - 1;
        }
 
        @Override
        public void close() throws IOException {
            lastIndexRetrieved = body.length - 1;
            body = null;
        }
    }
}

三、创建过滤器,通过过滤器包装原有的request对象

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
/**
 * @描述
 * @创建人 caoju
 */
@Component
public class RequestReplaceFilter extends OncePerRequestFilter {
 
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        if (!(request instanceof MyServletRequestWrapper)) {
            request = new MyServletRequestWrapper(request);
        }
        filterChain.doFilter(request, response);
    
        /*//如果有文件上传的业务场景,需要用下面的代码进行处理,不然文件上传的流会有问题
        String contentType = request.getContentType();
        //如果contentType是空
        //或者contentType是多媒体的上传类型则忽略,不进行包装,直接return
        if (contentType == null) {
            filterChain.doFilter(request, response);
            return;
        }else if(request.getContentType().startsWith("multipart/")){
            filterChain.doFilter(request, response);
            return;
        }else if (!(request instanceof MyServletRequestWrapper)) {
            request = new MyServletRequestWrapper(request);
        }
        filterChain.doFilter(request, response);
        */
    }
}

通过以上几步,我们就实现了把request里的 body 保存在 MyServletRequestWrapper中的效果

就可以在整个请求链路中任何地方去重复的获取request流了

四、使用案例

配置好之后,就可以在整个请求链路中任何地方去重复的获取request流了。

比如,你可以在请求刚进来时,在过滤器或者拦截器里拿到request对象,再拿到request对象的流数据,去做一些事情,或者你也可以在请求即将结束时,在统一的异常处理器中拿到request对象,拿到request对象流数据里请求的json参数;等等等等,还有其他很多你想使用的场景,都可以这么做。

下面是在代码中利用RequestContextHolder获取request对象,拿到request对象后就可以获取请求方式、请求url、以及请求参数这些数据了。如果你在某些地方也有需要打印记录请求方式、请求url、请求参数的这些需求,那可以直接复制粘贴我下边的代码就ok了

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        String mode = "";
        String methodUrl = "";
        String param = "";
        if (requestAttributes != null) {
            ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
            HttpServletRequest request = attributes.getRequest();
            //请求方式
            mode = request.getMethod();
            //方法URL
            methodUrl = request.getRequestURI();
            if(mode.equals(HttpMethod.GET.name())){
                param = request.getQueryString();
            }
            if(mode.equals(HttpMethod.POST.name())){
                param = getJsonRequest(request);
            }
        }
    /**
     * 获取Request中的JSON字符串
     * @param request
     * @return
     * @throws IOException
     */
    public static String getJsonRequest(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        try (BufferedReader reader = request.getReader();) {
            char[] buff = new char[1024];
            int len;
            while ((len = reader.read(buff)) != -1) {
                sb.append(buff, 0, len);
            }
        } catch (IOException e) {
            log.error("POST请求参数获取异常", e);
        }
        return sb.toString();
    }

ok,到这里解决request流只能获取一次的问题就搞定了

希望对你有所帮助

以上就是解决Java项目中request流只能获取一次的问题的详细内容,更多关于Java request流获取一次的资料请关注脚本之家其它相关文章!

相关文章

  • Struts2动态结果集代码示例

    Struts2动态结果集代码示例

    这篇文章主要介绍了Struts2动态结果集的有关内容,涉及具体代码示例,具有一定参考价值,需要的朋友可以了解下。
    2017-09-09
  • Java Hashtable机制深入了解

    Java Hashtable机制深入了解

    HashTable是jdk 1.0中引入的产物,基本上现在很少使用了,但是会在面试中经常被问到。本文就来带大家一起深入了解一下Hashtable,需要的可以参考一下
    2022-09-09
  • Java swing 图像处理多种效果实现教程

    Java swing 图像处理多种效果实现教程

    这篇文章主要介绍了Java swing 图像处理多种效果实现教程,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • springboot 中 inputStream 神秘消失之谜(终破)

    springboot 中 inputStream 神秘消失之谜(终破)

    这篇文章主要介绍了springboot 中 inputStream 神秘消失之谜,为了能够把这个问题说明,我们首先需要从简单的http调用说起,通过设置body等一些操作,具体实现代码跟随小编一起看看吧
    2021-08-08
  • MyBatis多对多一对多关系查询嵌套处理

    MyBatis多对多一对多关系查询嵌套处理

    这篇文章主要为大家介绍了MyBatis多对多一对多关系查询嵌套处理示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-10-10
  • Java给PDF加水印并合并多个文件

    Java给PDF加水印并合并多个文件

    大家好,本篇文章主要讲的是Java给PDF加水印并合并多个文件,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-02-02
  • springboot解析自定义yml方式

    springboot解析自定义yml方式

    这篇文章主要介绍了springboot解析自定义yml方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • SpringSecurity跨域请求伪造(CSRF)的防护实现

    SpringSecurity跨域请求伪造(CSRF)的防护实现

    本文主要介绍了SpringSecurity跨域请求伪造(CSRF)的防护实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • Java中字符序列的替换与分解的几种实现方法

    Java中字符序列的替换与分解的几种实现方法

    本文主要介绍了Java中字符序列的替换与分解的几种实现方法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • Mybatis中mapper.xml实现热加载介绍

    Mybatis中mapper.xml实现热加载介绍

    大家好,本篇文章主要讲的是Mybatis中mapper.xml实现热加载介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01

最新评论