关于解决使用过滤器造成IO流只能读取一次的问题

720 阅读1分钟

前言

SpringBoot项目在使用了拦截器(比如读取请求流进行解码)后你会发现你在controller中使用了@RequestBody注解获取参数报如下错误:

拦截器 I/O error while reading input message; nested exception is java.io.IOException: Stream closed

原因就是IO流关闭只能读取一次

解决办法

定义包装类,将IO流保存起来,并重写getInputStream()方法

 @Slf4j
 public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
     private byte[] requestBody;
 ​
     public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
         super(request);
         try (ServletInputStream inputStream = request.getInputStream()) {
             this.requestBody = ByteStreams.toByteArray(inputStream);
         }
     }
 ​
     @Override
     public ServletInputStream getInputStream() {
         ByteArrayInputStream stream = new ByteArrayInputStream(requestBody);
         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() {
                 return stream.read();
             }
         };
     }
 ​
     @Override
     public BufferedReader getReader() {
         return new BufferedReader(new InputStreamReader(getInputStream()));
     }
 }

定义过滤器将Request对象转成包装对象

 @Slf4j
 @WebFilter(filterName = "bodyReaderFilter", urlPatterns = "/*")
 @Component
 public class BodyReaderFilter implements Filter {
 ​
     @Override
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
         if (!(request instanceof HttpServletRequest)) {
             chain.doFilter(request, response);
             return;
         }
         BodyReaderHttpServletRequestWrapper bodyReaderHttpServletRequestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
         chain.doFilter(bodyReaderHttpServletRequestWrapper, response);
     }
 }

多说一句

过滤器和拦截器的顺序关系

  1. Filter Pre (过滤器) chain.doFilter(request, response) 前的逻辑

  2. service (Servlet) spring mvc的doService()方法,也是servlet的service()方法

  3. dispatcher (SpringMVC) 请求分发

  4. preHandle (拦截器) 进入interceptor

  5. controller (控制器) 处理业务需求

  6. postHandle (拦截器) 在controller逻辑后return ModelAndView前调整ModelAndView的内容

  7. afterCompletion (过滤器) 在filter返回前执行

  8. Filter After (过滤器) 过滤器执行后