You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring中HttpServletRequest读取后内容为空的解决方案咨询

问题解决:Spring中多次读取HttpServletRequest请求体的方案

问题原因

HttpServletRequest的输入流是单向只读的,读完一次后指针就走到流的末尾了,后续再调用getInputStream()或者getReader()自然读不到内容——这就是Class A读取后Class B拿不到请求体的根本原因。

解决方案:自定义请求包装类缓存请求体

核心思路是提前把请求体缓存起来,让后续所有读取操作都从缓存中获取,而不是直接读原始输入流。具体步骤如下:

1. 编写CachedHttpServletRequest包装类

继承HttpServletRequestWrapper,把请求体缓存成字节数组,重写getInputStream()和getReader()方法,每次都从缓存的字节数组生成新的流:

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;

public class CachedHttpServletRequest extends HttpServletRequestWrapper {
    private byte[] cachedBody;

    public CachedHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        // 读取原始请求体并缓存
        InputStream requestInputStream = request.getInputStream();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = requestInputStream.read(buffer)) != -1) {
            byteArrayOutputStream.write(buffer, 0, bytesRead);
        }
        cachedBody = byteArrayOutputStream.toByteArray();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedServletInputStream(cachedBody);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
        return new BufferedReader(new InputStreamReader(byteArrayInputStream, StandardCharsets.UTF_8));
    }

    // 自定义ServletInputStream实现,从缓存读取
    private static class CachedServletInputStream extends ServletInputStream {
        private final ByteArrayInputStream inputStream;

        public CachedServletInputStream(byte[] cachedBody) {
            this.inputStream = new ByteArrayInputStream(cachedBody);
        }

        @Override
        public int read() throws IOException {
            return inputStream.read();
        }

        @Override
        public boolean isFinished() {
            return inputStream.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readListener) {}
    }
}

2. 编写Filter替换原始请求

创建一个Filter,在请求到达业务类之前,把原始请求替换成我们的缓存请求:

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@WebFilter(urlPatterns = "/*") // 可根据业务调整拦截路径
public class RequestBodyCacheFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if (request instanceof HttpServletRequest) {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            // 只包装有请求体的请求方法,比如POST、PUT
            String method = httpRequest.getMethod();
            if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) {
                CachedHttpServletRequest cachedRequest = new CachedHttpServletRequest(httpRequest);
                chain.doFilter(cachedRequest, response);
                return;
            }
        }
        chain.doFilter(request, response);
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void destroy() {}
}

3. 注册Filter(可选)

如果项目没启用@WebFilter自动扫描,就在Spring配置类里手动注册:

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<RequestBodyCacheFilter> requestBodyCacheFilter() {
        FilterRegistrationBean<RequestBodyCacheFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new RequestBodyCacheFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(1); // 确保这个Filter在其他需要读请求体的组件之前执行
        return registrationBean;
    }
}

4. 优化原有读取代码(可选)

现在请求体可以重复读取,原来的extractBody方法可以简化:

public static String extractBody(HttpServletRequest request) {
    try (BufferedReader reader = request.getReader()) {
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
        return sb.toString();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

注意事项

  • 缓存请求体会占用内存,处理大文件上传等场景时要注意内存限制,可添加请求大小判断逻辑。
  • 调整Filter的拦截路径,避免对不需要读请求体的请求做包装。
  • 如果用了Spring Security,要确保这个Filter的执行顺序在Security过滤器之前,否则Security读取请求体后还是会出现问题。

内容的提问来源于stack exchange,提问作者RedBlue

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 22:09:56