获取@RequestBody请求的参数信息报错:java.io.IOException Stream closed

2022-06-23 09:57:47
问题描述

项目中采用spring aop进行日志记录,在切面类通知方法中编写日志逻辑时,需要获取HttpSevletRequest 中的请求参数;对于普通参数来说,没有任何问题,但是当请求方式为POST/PUT 并并且是@RequestBody 标记的请求,在获取JSON参数时,会出现java.io.IOException: Stream closed 异常;

原因

HttpServletReqeust获取输入流时仅允许读取一次,spring已经对@ReqeustBody 提前进行了处理,通过断点调试发现,aop代码中获取request.getInputStream() 时,输入流已经关闭,因此出现流已经关闭的异常;

异常信息:

java.io.IOException: Stream closed
at org.apache.catalina.connector.InputBuffer.read(InputBuffer.java:372)
at org.apache.catalina.connector.CoyoteInputStream.read(CoyoteInputStream.java:190)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.dongzz.cms.common.utils.WebUtil.getParamsJson(WebUtil.java:151)
at com.dongzz.cms.common.annotation.aspect.LogHandlerAdvice.recordSysLogs(LogHandlerAdvice.java:83)
at com.dongzz.cms.common.annotation.aspect.LogHandlerAdvice.handleSysLogs(LogHandlerAdvice.java:44)

解决思路

重新构建ServletRequest,读取输入流后进行缓存,然后重写进流里面,使请求输入流支持二次读取;

  1. 自定义HttpServletRequestWrapper,重新构建请求对象;

    packagecom.dongzz.cms.common.filter;importjavax.servlet.ServletInputStream;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletRequestWrapper;importjava.io.*;importjava.nio.charset.StandardCharsets;/**
     * 重新构建 ServletRequest,读取输入流后进行缓存,然后重写进流里面,使请求输入流支持二次读取
     */publicclassRequestWrapperextendsHttpServletRequestWrapper{privatefinalbyte[] body;publicRequestWrapper(HttpServletRequest request)throwsIOException{super(request);
            body=read(request.getInputStream()).getBytes(StandardCharsets.UTF_8);}@OverridepublicBufferedReadergetReader()throwsIOException{returnnewBufferedReader(newInputStreamReader(getInputStream()));}@OverridepublicServletInputStreamgetInputStream()throwsIOException{ByteArrayInputStream bis=newByteArrayInputStream(body);returnnewServletInputStream(){@Overridepublicintread()throwsIOException{return bis.read();}};}/**
         * 读取输入流中的参数,转化为字符串
         *
         * @param is
         * @return
         */privateStringread(InputStream is){StringBuilder sb=newStringBuilder();BufferedReader reader=newBufferedReader(newInputStreamReader(is,StandardCharsets.UTF_8));try{String line;while((line= reader.readLine())!=null){
                    sb.append(line);}}catch(Exception e){
                e.printStackTrace();}finally{if(null!= reader){try{
                        reader.close();}catch(IOException e){
                        e.printStackTrace();}}}return sb.toString();}}
  2. 自定义过滤器,用自定义的 ServletRequest 处理@RequestBody 特殊请求;

    packagecom.dongzz.cms.common.filter;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importorg.springframework.http.HttpMethod;importorg.springframework.http.MediaType;importjavax.servlet.*;importjavax.servlet.annotation.WebFilter;importjavax.servlet.http.HttpServletRequest;importjava.io.IOException;/**
     * 解决读取 @RequestBody 相关参数时,HttpServletRequest的输入流只能读取一次的问题
     * 问题表现: java.io.IOException: Stream closed
     */@WebFilter(
            filterName="bodyFilter",
            urlPatterns="/*")publicclassRequestBodyFilterimplementsFilter{publicstaticfinalLogger logger=LoggerFactory.getLogger(RequestBodyFilter.class);@Overridepublicvoidinit(FilterConfig config)throwsServletException{
            logger.debug("init request boy filter.");}@OverridepublicvoiddoFilter(ServletRequest servletRequest,ServletResponse servletResponse,FilterChain chain)throwsIOException,ServletException{ServletRequest requestWrapper=null;if(servletRequestinstanceofHttpServletRequest){HttpServletRequest request=(HttpServletRequest) servletRequest;String mehtod= request.getMethod();String contentType= request.getContentType();// 特殊处理 POST/PUT请求,忽略上传请求if((HttpMethod.POST.name().equals(mehtod)||HttpMethod.PUT.name().equals(mehtod))&&(!MediaType.MULTIPART_FORM_DATA_VALUE.equals(contentType))){
                    requestWrapper=newRequestWrapper(request);// 重新构建请求,新的对象读取输入流后进行缓存,然后重写进流,因此支持二次读取}}if(null== requestWrapper){
                chain.doFilter(servletRequest, servletResponse);}else{
                chain.doFilter(requestWrapper, servletResponse);}}@Overridepublicvoiddestroy(){
            logger.debug("destroy request body filter.");}}

此时在 aop 通知方法中通过 HttpServletRequest 获取 @RequestBody 参数时能够取得相关的参数值,不会出现流已关闭的异常;

  • 作者:暴走编程
  • 原文链接:https://blog.csdn.net/zwk1066448989/article/details/122486298
    更新时间:2022-06-23 09:57:47