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

SpringBoot中使用RequestWrapper后@RequestParam获取参数失败问题排查

问题原因分析
  1. ServletInputStream实现不完整
    你自定义的MyRequestWrapper中,重写getInputStream()返回的ServletInputStream仅实现了read()方法,但Servlet 3.1及以上规范要求必须实现isFinished()、isReady()、setReadListener()三个方法。Spring MVC处理请求时会调用这些方法,未实现会引发底层异常,导致application/x-www-form-urlencoded类型的请求参数无法正常解析。

  2. 请求流与参数解析的冲突
    对于application/x-www-form-urlencoded请求,Servlet容器需要读取请求体(InputStream/Reader)来解析参数并存入parameterMap。如果包装后的请求流处理不当,容器无法正确读取请求体,就会导致@RequestParam无法获取参数,抛出MissingServletRequestParameterException。

  3. 过滤器的request包装错误
    你提供的过滤器代码中直接将ServletRequest强转为MyRequestWrapper是错误的,正确做法是基于原生HttpServletRequest创建MyRequestWrapper实例,否则会引发ClassCastException(推测你实际代码中已创建实例,但流处理仍存在问题)。


解决方案

方案1:完善ServletInputStream实现

补全ServletInputStream的所有抽象方法,确保容器能正常处理请求流,同时统一缓存所有类型请求的请求体:

public class MyRequestWrapper extends HttpServletRequestWrapper {
    private byte[] cachedBody;
    private ByteArrayInputStream inputStream;

    public MyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
        this.inputStream = new ByteArrayInputStream(this.cachedBody);
    }

    @Override
    public ServletInputStream getInputStream() {
        return new ServletInputStream() {
            @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) {
                throw new UnsupportedOperationException("异步读取未实现");
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding()));
    }

    public String getBody() {
        return new String(cachedBody, getCharacterEncoding());
    }
}

方案2:适配x-www-form-urlencoded的参数解析

如果不需要缓存x-www-form-urlencoded的原始请求体,只想保留参数解析能力,可缓存原请求的参数Map,同时重写参数相关方法:

public class MyRequestWrapper extends HttpServletRequestWrapper {
    private byte[] cachedBody = new byte[]{};
    private ByteArrayInputStream inputStream;
    private Map<String, String[]> parameterMap;

    public MyRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.parameterMap = new HashMap<>(request.getParameterMap());
        
        if (request.getContentType() != null && request.getContentType().contains("multipart/")) {
            inputStream = null;
        } else if (request.getContentType() != null && request.getContentType().contains("application/x-www-form-urlencoded")) {
            // 拼接参数作为请求体内容
            StringBuilder bodyBuilder = new StringBuilder();
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                for (String value : entry.getValue()) {
                    if (bodyBuilder.length() > 0) bodyBuilder.append("&");
                    bodyBuilder.append(URLEncoder.encode(entry.getKey(), getCharacterEncoding()))
                            .append("=")
                            .append(URLEncoder.encode(value, getCharacterEncoding()));
                }
            }
            cachedBody = bodyBuilder.toString().getBytes(getCharacterEncoding());
            inputStream = new ByteArrayInputStream(cachedBody);
        } else {
            cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
            inputStream = new ByteArrayInputStream(cachedBody);
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return inputStream == null ? super.getInputStream() : new ServletInputStream() {
            @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) {
                throw new UnsupportedOperationException("异步读取未实现");
            }
        };
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return inputStream == null ? super.getReader() : new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding()));
    }

    @Override
    public String getParameter(String name) {
        String[] values = parameterMap.get(name);
        return values != null && values.length > 0 ? values[0] : null;
    }

    @Override
    public String[] getParameterValues(String name) {
        return parameterMap.get(name);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return new HashMap<>(parameterMap);
    }

    public String getBody() {
        return new String(cachedBody, getCharacterEncoding());
    }
}

方案3:修正过滤器的包装逻辑

确保过滤器中正确创建MyRequestWrapper实例,而非直接强转:

@WebFilter(filterName = "RequestResponseFilter", urlPatterns = "/*", asyncSupported = true)
public class RequestResponseFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        MyRequestWrapper requestWrapper = new MyRequestWrapper(httpRequest);
        
        System.out.println("请求体:" + requestWrapper.getBody());
        
        chain.doFilter(requestWrapper, response);
    }
}

额外优化:仅处理JSON请求

如果你的需求只是打印application/json类型的请求体,可在过滤器中判断contentType,仅对JSON请求进行包装,避免影响其他类型请求的参数解析:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    String contentType = httpRequest.getContentType();
    
    if (contentType != null && contentType.contains("application/json")) {
        MyRequestWrapper requestWrapper = new MyRequestWrapper(httpRequest);
        System.out.println("JSON请求体:" + requestWrapper.getBody());
        chain.doFilter(requestWrapper, response);
    } else {
        System.out.println("非JSON请求参数:" + httpRequest.getParameterMap());
        chain.doFilter(request, response);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 00:04:03