【JsonCachingRequestWrapper】改造spring MVC ContentCachingRequestWrapper支持JSON

本文介绍了如何在Java中解决因InputStream限制导致的Controller层参数解析错误,通过自定义JsonCachingRequestWrapper类实现对JSON请求的多次读取。实现原理包括使用byte[]缓存输入流内容,并展示了如何在过滤器中应用和判断请求类型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

因为Java的inputstream只能读一次,所以ServletRequest中的inputstream一旦中途被读过一次,controller层在组装参数时因为读不到数据就会报错(因为再读inputstream时发现为空)

而spring提供了一个ContentCachingRequestWrapper,但是只支持application/x-www-form-urlencoded类型的请求,对于JSON是不支持的。并且它不支持多次读inputstream,只是把读完之后的内容cache了起来,然后我们可以通过getContentAsByteArray()方法获取cache住的内容,而我们要实现的是一个可以多次读流的request。

实现 JsonCachingRequestWrapper

实现思路

实现思路很简单,将inputstream读入一个byte[],后续每次需要使用inputstream时,通过byte[]实例化一个新的inputstream。当然这里还有一个知识点,servlet提供了一个HttpServletRequestWrapper基础类,让我们可以通过继承这个类来改造request的行为。下面是完整的代码(可以直接复制粘贴使用):

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

/**
 * @author xchen
 */
public class JsonCachingRequestWrapper extends HttpServletRequestWrapper {
    private static final String CONTENT_TYPE_JSON = "application/json";
    private static final String POST_STR = "POST";

    private byte[] cache;

    public JsonCachingRequestWrapper(HttpServletRequest request) {
        super(request);
        int contentLength = request.getContentLength();
        writeRequestParametersToCachedContent(contentLength);
    }

    public static boolean isSupport(ServletRequest request) {
        String contentType = request.getContentType();
        String scheme = request.getScheme();
        if (null == contentType || scheme == null) {
            return false;
        }
        if (scheme.equals("http") || scheme.equals("https")) {
            HttpServletRequest req = (HttpServletRequest) request;
            if (!POST_STR.equals(req.getMethod())) {
                return false;
            }
            return contentType.toLowerCase().startsWith(CONTENT_TYPE_JSON);
        }
        return false;
    }


    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new BodyInputStream(this.cache);
    }

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


    private void writeRequestParametersToCachedContent(int contentLength) {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream(contentLength);
            byte[] buffer = new byte[256];
            ServletInputStream inputStream = super.getInputStream();
            while (!inputStream.isFinished()) {
                int nums = inputStream.read(buffer);
                if (nums > 0) {
                    outputStream.write(buffer, 0, nums);
                }
            }
            this.cache = outputStream.toByteArray();
        } catch (IOException ex) {
            throw new IllegalStateException("Failed to write request parameters to cached content", ex);
        }
    }

    public byte[] getContentAsByteArray() {
        return this.cache;
    }


    private static class BodyInputStream extends ServletInputStream {

        private final InputStream delegate;
        private boolean isFinished = false;

        public BodyInputStream(byte[] body) {
            this.delegate = new ByteArrayInputStream(body);
        }

        @Override
        public boolean isFinished() {
            return isFinished;
        }

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

        @Override
        public void setReadListener(ReadListener readListener) {
            throw new UnsupportedOperationException();
        }

        @Override
        public int read() throws IOException {
            int read = this.delegate.read();
            if (read == -1) {
                isFinished = true;
            }
            return read;
        }

        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            int read = this.delegate.read(b, off, len);
            if (read == -1) {
                isFinished = true;
            }
            return read;
        }

        @Override
        public int read(byte[] b) throws IOException {
            int read = this.delegate.read(b);
            if (read == -1) {
                isFinished = true;
            }
            return read;
        }

        @Override
        public long skip(long n) throws IOException {
            return this.delegate.skip(n);
        }

        @Override
        public int available() throws IOException {
            return this.delegate.available();
        }

        @Override
        public void close() throws IOException {
            this.delegate.close();
        }

        @Override
        public synchronized void mark(int readlimit) {
            this.delegate.mark(readlimit);
        }

        @Override
        public synchronized void reset() throws IOException {
            this.delegate.reset();
        }

        @Override
        public boolean markSupported() {
            return this.delegate.markSupported();
        }
    }
}

如何使用

细心的朋友会发现上面有一个方法JsonCachingRequestWrapper.isSupport(ServletRequest request).这个方法可以用来判断是否是JSON类型的请求,如果是表单类型或者是上传文件,显然用JsonCachingRequestWrapper是不合适的。

  • 初始化,通常我们可以这么初始化(一般放在filter中):
public class RequestCommonFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        if (JsonCachingRequestWrapper.isSupport(servletRequest)) {
            servletRequest = new JsonCachingRequestWrapper((HttpServletRequest) servletRequest);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
}
  • 使用(这里以打印日志为例)
if (JsonCachingRequestWrapper.isSupport(request)) {
    JsonCachingRequestWrapper requestWrapper = (JsonCachingRequestWrapper) request;
    String reqParams = new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8);
    log.info("Req-log: {} {}", requestWrapper.getRequestURI(), reqParams);
}

写在最后

JavaIO的知识点还是挺多的,一开始学也比较抽象,要学的好还得懂一点操作系统,值得大家找资料仔细学习下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值