使用ServletInputStream在拦截器或过滤器中应用后重写

 更新时间:2021年10月26日 11:08:35   作者:闫-先生  
这篇文章主要介绍了使用ServletInputStream在拦截器或过滤器中应用后重写,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

ServletInputStream在拦截器或过滤器应用后重写

ServletInputStream inputStream = super.getInputStream();
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
try {
    reader = new BufferedReader(new InputStreamReader(servletInputStream, Charset.forName("UTF-8")));
    String line = "";
    while ((line = reader.readLine()) != null) {
          sb.append(line);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (servletInputStream != null) {
       try {
           servletInputStream.close();
       } catch (IOException e) {
           e.printStackTrace();
       }
    }
    if (reader != null) {
         try {
              reader.close();
         } catch (IOException e) {
              e.printStackTrace();
         }
    }
}
//使用ServletInputStream中数据的代码
byte[] bytes = sb.getBytes("UTF-8");
final ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
return new ServletInputStream() {
       @Override
       public boolean isFinished() {
               return false;
       }
 
        @Override
        public boolean isReady() {
               return false;
        }
 
        @Override
        public void setReadListener(ReadListener readListener) {
 
        }
 
        @Override
        public int read() throws IOException {
               return bais.read();
        }
};

在拦截器种使用了request.getInputStream()或者getReader()

导致在controller中无法获取请求参数

问题描述

在拦截器种使用了request.getInputStream()或者getReader(),然后在controller接口种使用了@requestbody ,导致controller中无法获取入参,报错:HttpMessageNotReadableException: Required request body is missing:

原因分析

ServletRequest中getReader()和getInputStream()只能调用一次。而又由于@RequestBody注解获取输出参数的方式也是根据流的方式获取的。所以我们前面使用流获取后,后面的@RequestBody就获取不到对应的输入流了。

为什么取不到输入流了???因为流对应的是数据,数据放在内存中,有的是部分放在内存中。

read 一次标记一次当前位置(mark position),第二次read就从标记位置继续读(从内存中copy)数据。

所以这就是为什么读了一次第二次是空了。 怎么让它不为空呢?只要inputstream 中的pos 变成0就可以重写读取当前内存中的数据。

javaAPI中有一个方法public void reset() 这个方法就是可以重置pos为起始位置,但是不是所有的IO读取流都可以调用该方法!ServletInputStream是不能调用reset方法,这就导致了只能调用一次getInputStream()。

如何处理

重写HttpServletRequestWrapper把request保存下来,然后通过过滤器把保存下来的request再填充进去,这样就可以多次读取request了。

第一步:定义过滤器

 
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
 
@WebFilter(urlPatterns = "/*", filterName = "channelFilter")
public class ChannelFilter implements Filter {
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}
 
	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
			throws IOException, ServletException {
		ServletRequest requestWrapper = null;
		if (servletRequest instanceof HttpServletRequest) {
			requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest);
		}
		if (requestWrapper == null) {
			filterChain.doFilter(servletRequest, servletResponse);
		} else {
			System.out.println("进入了过滤器。。。。。");
			filterChain.doFilter(requestWrapper, servletResponse);
		}
	}
 
	@Override
	public void destroy() {
	}
}

第二步:重写RequestWrapper类

 
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
 
public class RequestWrapper extends HttpServletRequestWrapper { 
	private final String body; 
	public RequestWrapper(HttpServletRequest request) {
		super(request);
		StringBuilder stringBuilder = new StringBuilder();
		BufferedReader bufferedReader = null;
		InputStream inputStream = null;
		try {
			inputStream = request.getInputStream();
			if (inputStream != null) {
				bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
				char[] charBuffer = new char[128];
				int bytesRead = -1;
				while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
					stringBuilder.append(charBuffer, 0, bytesRead);
				}
			} else {
				stringBuilder.append("");
			}
		} catch (IOException ex) {
		} finally {
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (bufferedReader != null) {
				try {
					bufferedReader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		body = stringBuilder.toString();
	} 
	@Override
	public ServletInputStream getInputStream() throws IOException {
		final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
		ServletInputStream servletInputStream = new ServletInputStream() {
			@Override
			public boolean isFinished() {
				return false;
			}
 
			@Override
			public boolean isReady() {
				return false;
			}
 
			@Override
			public void setReadListener(ReadListener readListener) {
			}
 
			@Override
			public int read() throws IOException {
				return byteArrayInputStream.read();
			}
		};
		return servletInputStream;
	}
 
	@Override
	public BufferedReader getReader() throws IOException {
		return new BufferedReader(new InputStreamReader(this.getInputStream()));
	} 
	public String getBody() {
		return this.body;
	}
}

第三步:在启动类中注册过滤器

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

相关文章

  • spring boot使用@Async注解解决异步多线程入库的问题

    spring boot使用@Async注解解决异步多线程入库的问题

    最近在写项目是需要添加异步操作来提高效率,所以下面这篇文章主要给大家介绍了关于spring boot使用@Async注解解决异步多线程入库问题的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-05-05
  • Java如何使用字符流读写非文本文件

    Java如何使用字符流读写非文本文件

    这篇文章主要介绍了Java如何使用字符流读写非文本文件,以Java的字符流读取文件为例:它只能读取0-65535之间的字符,可以看出来字符都是正数,但是二进制的byte是可以为负数的,需要的朋友可以参考下
    2023-04-04
  • 深入讲解SpringBoot Actuator是什么

    深入讲解SpringBoot Actuator是什么

    Spring Boot Actuator提供了生产上经常用到的功能(如健康检查,审计,指标收集,HTTP跟踪等),帮助我们监控和管理Spring Boot应用程序。这些功能都可以通过JMX或HTTP端点访问
    2023-01-01
  • 一文吃透Spring集成MyBatis

    一文吃透Spring集成MyBatis

    spring能集成很多的框架,是spring一个优势功能,通过集成功能,让开发人员使用其他框架更方便,本文将给大家详细介绍Spring如何集成MyBatis,,需要的朋友可以参考下
    2023-05-05
  • JDK的命令详解

    JDK的命令详解

    JDK的命令详解...
    2006-12-12
  • SpringBoot自定义starter启动器的实现思路

    SpringBoot自定义starter启动器的实现思路

    这篇文章主要介绍了SpringBoot如何自定义starter启动器,通过starter的自定义过程,能够加深大家对SpringBoot自动配置原理的理解,需要的朋友可以参考下
    2022-10-10
  • 详解springboot中各个版本的redis配置问题

    详解springboot中各个版本的redis配置问题

    这篇文章主要介绍了详解springboot中各个版本的redis配置问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • SpringBoot使用TraceId进行日志链路追踪的实现步骤

    SpringBoot使用TraceId进行日志链路追踪的实现步骤

    有时候一个业务调用链场景,很长,调了各种各样的方法,看日志的时候,各个接口的日志穿插,确实让人头大,所以为了解决这个问题,本文给大家介绍了SpringBoot使用TraceId进行日志链路追踪的实现步骤,需要的朋友可以参考下
    2024-11-11
  • Android中比较常见的Java super关键字

    Android中比较常见的Java super关键字

    这篇文章主要为大家介绍了Android中比较常见的Java super关键字,具有一定的学习参考价值,感兴趣的小伙伴们可以参考一下
    2016-01-01
  • Java实现优雅的参数校验方法详解

    Java实现优雅的参数校验方法详解

    这篇文章主要为大家详细介绍了Java语言如何实现优雅的参数校验,文中的示例代码讲解详细,对我们学习Java有一定是帮助,需要的可以参考一下
    2022-06-06

最新评论