如何通过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
相关产品推荐
相关产品推荐

