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

如何通过Spring拦截器获取请求响应内容大小?

解决Spring拦截器中获取响应内容大小的问题

这个问题我之前也碰到过——原生的HttpServletResponse确实没有直接获取响应体大小的方法,因为响应是流式输出的,数据会直接写给客户端,没办法事后回溯读取。不过我们可以通过包装响应对象来解决这个问题,具体实现如下:

1. 自定义响应包装类

首先创建一个继承HttpServletResponseWrapper的包装类,它会拦截响应的输出流,统计写入的字节数:

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;

public class ContentLengthResponseWrapper extends HttpServletResponseWrapper {
    private final CountingOutputStream countingOutputStream;
    private PrintWriter writer;

    public ContentLengthResponseWrapper(HttpServletResponse response) {
        super(response);
        this.countingOutputStream = new CountingOutputStream(response.getOutputStream());
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        return countingOutputStream;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if (writer == null) {
            writer = new PrintWriter(countingOutputStream);
        }
        return writer;
    }

    // 获取统计到的响应内容长度
    public long getContentLength() {
        return countingOutputStream.getByteCount();
    }

    // 内部类:统计输出字节数的OutputStream
    private static class CountingOutputStream extends OutputStream {
        private final OutputStream delegate;
        private long byteCount;

        public CountingOutputStream(OutputStream delegate) {
            this.delegate = delegate;
            this.byteCount = 0;
        }

        @Override
        public void write(int b) throws IOException {
            delegate.write(b);
            byteCount++;
        }

        @Override
        public void write(byte[] b) throws IOException {
            delegate.write(b);
            byteCount += b.length;
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            delegate.write(b, off, len);
            byteCount += len;
        }

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

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

        public long getByteCount() {
            return byteCount;
        }
    }
}

2. 修改拦截器代码

接下来调整你的DataUsageInterceptor,在请求处理前包装响应对象,并在请求完成后从包装类中获取响应长度:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class DataUsageInterceptor extends HandlerInterceptorAdapter {
    private static final Logger LOG = LoggerFactory.getLogger(DataUsageInterceptor.class);
    // 用于在request中存储包装后的响应对象的属性名
    private static final String RESPONSE_WRAPPER_ATTR = "CONTENT_LENGTH_WRAPPER";

    @Override
    public boolean preHandle(
        HttpServletRequest request, HttpServletResponse response, Object handler
    ) throws Exception {
        // 统计请求内容长度,用getContentLengthLong避免大请求溢出
        long requestLength = request.getContentLengthLong();
        LOG.info("Request content length: {} bytes", requestLength);
        System.out.println("---- Request length: " + requestLength + "----------");

        // 包装响应对象并存入request属性
        ContentLengthResponseWrapper wrappedResponse = new ContentLengthResponseWrapper(response);
        request.setAttribute(RESPONSE_WRAPPER_ATTR, wrappedResponse);

        // 传递包装后的响应给后续处理链
        return super.preHandle(request, wrappedResponse, handler);
    }

    @Override
    public void afterCompletion(
        HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex
    ) throws Exception {
        // 从request中取出包装后的响应对象
        ContentLengthResponseWrapper wrappedResponse =
                (ContentLengthResponseWrapper) request.getAttribute(RESPONSE_WRAPPER_ATTR);

        if (wrappedResponse != null) {
            // 确保缓冲区内容全部写入,避免统计不全
            if (wrappedResponse.getWriter() != null) {
                wrappedResponse.getWriter().flush();
            } else {
                wrappedResponse.getOutputStream().flush();
            }

            long responseLength = wrappedResponse.getContentLength();
            LOG.info("Response content length: {} bytes", responseLength);
            System.out.println("---------- Response length: " + responseLength + " ----------------");
        }

        super.afterCompletion(request, response, handler, ex);
    }
}

关键注意点

  • 为什么用包装类:HttpServletResponseWrapper是Spring/Servlet提供的标准扩展方式,能在不破坏原有响应逻辑的前提下,拦截输出流进行统计。
  • 传递包装对象:必须在preHandle中把包装后的响应传递给后续处理链,否则Controller的响应会直接写入原始流,无法统计。
  • 缓冲区刷新:统计前一定要调用flush(),否则部分响应内容可能还在缓冲区中,导致统计的长度偏小。
  • 避免溢出:请求长度推荐用getContentLengthLong()替代getContentLength(),后者返回int类型,当请求体超过2GB时会溢出。
  • 日志修正:你原来的Logger引用了CheckController,建议改成当前拦截器类,方便日志定位。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:35:53