SpringMVC(请求包装器)解决流只能读取一次(学习笔记2021.01.21)

SpringMVC(请求包装器)解决流只能读取一次(学习笔记2021.01.21)

前言:

使用场景拦截器读取了request IO流方法request.getInputStream(), 然后到了我们的 (处理器/控制器/Controller), 在次调用request.getInputStream()的时候获取的流是空的, 或者通过流一次读取所有参数。

另一个场景, 在全局异常处理器中, 输出JSON请求参数日志需要通过流方式进行读取。

根据Servlet规范,如果同时满足下列条件,则请求体(Entity)中的表单数据,将被填充到request的parameter集合中(request.getParameter系列方法可以读取相关数据)

  1. 这是一个HTTP/HTTPS请求
  2. 请求方法是POST(queryString无论是否POST都将被设置到parameter中)
  3. 请求的类型(Content-Type头)是application/x-www-form-urlencoded
  4. Servlet调用了getParameter系列方法

这里的表单数据已经被填充到parameterMap中,不能再通过getInputStream获取。

1.0 使用装饰者模式, 重新包装HttpServletRequest对象请求

javax.servlet.http包下面有一个装饰器类装饰器类HttpServletRequestWrapper,利用这个装饰器类,我们可以重新包装一个HttpServletRequest对象。

package javax.servlet.http; // 包
public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest {
	类中方法省略..............
}

2.0 自定义装饰者模式, 重新包装

public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper{
    
    private final byte[] streamBody;
    private static final int BUFFER_SIZE = 4096;
    
    public InputStreamHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        // 读取流得到字节数组
        byte[] bytes = inputStreamToByteArray(request.getInputStream());
        streamBody = bytes;
    }

    /** 
     * 读取输入流, 读到字节数组输出流获取字节数组
     * 
     * @param inputStream 
     * @return byte[] 
     * @author: ZhiHao
     * @date: 2021/1/21 
     */
    private byte[] inputStreamToByteArray(InputStream inputStream) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        byte[] bytes = new byte[BUFFER_SIZE];
        int length;
        while ((length = inputStream.read(bytes, 0, BUFFER_SIZE)) != -1) {
            outputStream.write(bytes, 0, length);
        }
        return outputStream.toByteArray();
    }

    /** 
     * 重写获取流方法, 使用自定义ByteArrayInputStream, 便于多次读取
     * 每次调用getInputStream方法,其实是重新利用streamBody重新new一个流,所以可以多次读取
     *  
     * @return ServletInputStream 
     * @author: ZhiHao
     * @date: 2021/1/21 
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(streamBody);

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

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

            @Override
            public void setReadListener(ReadListener listener) {

            }

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

    /** 
     * 重写获取读流方法, 便于多次读取
     *  
     * @return java.io.BufferedReader 
     * @author: ZhiHao
     * @date: 2021/1/21 
     */
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
}

3.0 使用优先级最高的过滤器进行重新包装传递到后续过滤器与控制器

有了装饰器后,就要装饰目标对象。SpringMVC的一次请求会被一个个过滤器层层调用,也就是我们常说的责任链模式。利用Filter我们就可以在某个特定的位置装饰HttpServletRequest对象。

public class InputStreamWrapperFilter extends OncePerRequestFilter{

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        ServletRequest servletRequest = new InputStreamHttpServletRequestWrapper(httpServletRequest);
        filterChain.doFilter(servletRequest, httpServletResponse);
    }
}

注册过滤器

@Bean
@Order(1) // Spring Boot 会按照order值的大小,从小到大的顺序来依次过滤。
public FilterRegistrationBean inputStreamWrapperFilterRegistration() {
    FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    registrationBean.setFilter(new InputStreamWrapperFilter());
    registrationBean.setOrder(1);
    registrationBean.setName("inputStreamWrapperFilter");
    registrationBean.addUrlPatterns("/*");
    return registrationBean;
}

到此就可以多次进行request.getInputStream()方法读取流了

参考了: https://www.jianshu.com/p/2d0d72ce2aee
1

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

懵懵懂懂程序员

如果节省了你的时间, 请鼓励

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

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

打赏作者

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

抵扣说明:

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

余额充值