项目中采用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 ,读取输入流后进行缓存,然后重写进流里面,使请求输入流支持二次读取;

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

package com.dongzz.cms.common.filter;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;
 * 重新构建 ServletRequest,读取输入流后进行缓存,然后重写进流里面,使请求输入流支持二次读取
public class RequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;
    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        body = read(request.getInputStream()).getBytes(StandardCharsets.UTF_8);
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    @Override
    public ServletInputStream getInputStream() throws IOException {
        ByteArrayInputStream bis = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return bis.read();
     * 读取输入流中的参数,转化为字符串
     * @param is
     * @return
    private String read(InputStream is) {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = new BufferedReader(new InputStreamReader(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();
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
 * 解决读取 @RequestBody 相关参数时,HttpServletRequest的输入流只能读取一次的问题
 * 问题表现: java.io.IOException: Stream closed
@WebFilter(
        filterName = "bodyFilter",
        urlPatterns = "/*"
public class RequestBodyFilter implements Filter {
    public static final Logger logger = LoggerFactory.getLogger(RequestBodyFilter.class);
    @Override
    public void init(FilterConfig config) throws ServletException {
        logger.debug("init request boy filter.");
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        if (servletRequest instanceof HttpServletRequest) {
            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 = new RequestWrapper(request); // 重新构建请求,新的对象读取输入流后进行缓存,然后重写进流,因此支持二次读取
        if (null == requestWrapper) {
            chain.doFilter(servletRequest, servletResponse);
        } else {
            chain.doFilter(requestWrapper, servletResponse);
    @Override
    public void destroy() {
        logger.debug("destroy request body filter.");