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

如何在HttpServletRequest中重置请求输入流?Spring拦截器场景

解决Spring拦截器读取请求体后控制器获取为null的问题

这个问题我之前也碰到过,核心原因是HttpServletRequest的输入流是一次性读取的——拦截器里读完请求体后,输入流的指针就到末尾了,后续控制器再读取时就拿不到任何内容,自然返回null。不过咱们可以通过包装请求的方式,把请求体缓存起来实现重复读取,具体方案如下:

1. 自定义请求包装类,缓存请求体

首先创建一个继承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 CachedBodyHttpServletRequest extends HttpServletRequestWrapper {

    private final byte[] cachedBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        // 读取原始请求体并缓存到字节数组
        InputStream requestInputStream = request.getInputStream();
        this.cachedBody = requestInputStream.readAllBytes();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        // 返回基于缓存字节数组的输入流
        return new ServletInputStream() {
            private final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);

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

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

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

            @Override
            public void setReadListener(ReadListener readListener) {
                // 同步场景下无需实现,异步场景可按需补充
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8));
    }

    // 提供方法直接获取缓存的请求体字符串,方便拦截器日志记录
    public String getCachedBody() {
        return new String(cachedBody, StandardCharsets.UTF_8);
    }
}

2. 创建Filter,替换原始请求为包装后的请求

注意这里要用Filter而不是Interceptor,因为Filter的执行时机在DispatcherServlet之前,我们需要在请求到达拦截器和控制器之前就完成请求的包装,这样后续所有组件使用的都是可重复读取的请求:

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

@WebFilter(urlPatterns = "/*")
public class CachedBodyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ServletRequest wrappedRequest = request;
        // 判断是否为Http请求,替换为自定义包装类
        if (request instanceof HttpServletRequest) {
            wrappedRequest = new CachedBodyHttpServletRequest((HttpServletRequest) request);
        }
        // 传递包装后的请求到后续流程
        chain.doFilter(wrappedRequest, response);
    }

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

    @Override
    public void destroy() {}
}

3. 在拦截器中读取缓存的请求体

现在拦截器里就可以安全地读取请求体了,而且不会影响后续控制器的获取:

import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoggingInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 判断请求是否为我们包装后的类型
        if (request instanceof CachedBodyHttpServletRequest) {
            CachedBodyHttpServletRequest cachedRequest = (CachedBodyHttpServletRequest) request;
            String requestBody = cachedRequest.getCachedBody();
            // 这里执行日志记录逻辑,比如打印到控制台或存入日志系统
            System.out.println("请求体日志:" + requestBody);
        }
        return true;
    }
}

4. 配置组件生效

最后需要让Filter和Interceptor在Spring Boot中生效:

  • 对于Filter:在启动类上添加@ServletComponentScan注解,扫描自定义的Filter;
  • 对于Interceptor:配置WebMvcConfigurer注册拦截器:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor())
                .addPathPatterns("/**"); // 指定需要拦截的接口路径
    }
}

注意事项

如果你的系统有超大请求体的场景,缓存字节数组可能会占用较多内存,这时可以考虑添加请求体大小限制,或者只针对需要日志记录的接口做包装,避免不必要的内存消耗。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:50:43