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>
我们需要写一个自定义包装类,并继承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;
- }
- }
- }
- 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流只能获取一次的问题就搞定了
希望对你有所帮助