ContentCachingRequestWrapper 和 ContentCachingResponseWrapper

这两个类是 Spring Web 里非常实用但也很容易踩坑的工具,常见于:

👉 日志记录 / 请求响应体打印 / 审计 / 签名校验


🧠 一、核心一句话

👉 ContentCachingRequestWrapper 和 ContentCachingResponseWrapper 的本质:

把原本“只能读一次”的请求/响应流缓存起来,让你可以“重复读取”。


⚠️ 二、为什么需要它?

在 Servlet 规范中:

1️⃣ Request Body(请求体)

request.getInputStream()

👉 只能读一次!

读完就没了:

Filter 读了 → Controller 就读不到了 ❌

2️⃣ Response Body(响应体)

response.getOutputStream()

👉 也是写出去就没法再拿回来


✅ 三、ContentCachingRequestWrapper(请求缓存)

📌 作用

👉 缓存请求体(body),支持后续重复读取


📌 使用方式(典型 Filter)

ContentCachingRequestWrapper requestWrapper =
        new ContentCachingRequestWrapper(request);

filterChain.doFilter(requestWrapper, response);

// 在请求处理完成后读取
byte[] buf = requestWrapper.getContentAsByteArray();
String body = new String(buf, request.getCharacterEncoding());

📌 关键点(面试重点)

⚠️ 必须“先执行链,再读取”

filterChain.doFilter(...)

👉 否则缓存里是空的!

原因:

👉 它是“被动缓存”——只有在真正读取 InputStream 时才会缓存


📌 常见用途

  • 打印请求日志
  • 接口签名验证
  • 防重放攻击

✅ 四、ContentCachingResponseWrapper(响应缓存)

📌 作用

👉 缓存响应内容(body)


📌 使用方式

ContentCachingResponseWrapper responseWrapper =
        new ContentCachingResponseWrapper(response);

filterChain.doFilter(request, responseWrapper);

// 获取响应内容
byte[] buf = responseWrapper.getContentAsByteArray();
String body = new String(buf, response.getCharacterEncoding());

// ⚠️ 关键:必须手动写回响应
responseWrapper.copyBodyToResponse();

⚠️ 关键坑(高频面试点)

❗ 必须调用:

responseWrapper.copyBodyToResponse();

否则:

👉 客户端收不到响应!!


🧩 五、完整 Filter 示例(生产可用)

public class LoggingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request,
                         ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException {

        ContentCachingRequestWrapper req =
                new ContentCachingRequestWrapper((HttpServletRequest) request);

        ContentCachingResponseWrapper resp =
                new ContentCachingResponseWrapper((HttpServletResponse) response);

        chain.doFilter(req, resp);

        String requestBody = new String(
                req.getContentAsByteArray(),
                request.getCharacterEncoding()
        );

        String responseBody = new String(
                resp.getContentAsByteArray(),
                response.getCharacterEncoding()
        );

        System.out.println("REQ: " + requestBody);
        System.out.println("RESP: " + responseBody);

        // 必须写回
        resp.copyBodyToResponse();
    }
}

🔥 六、底层原理(面试加分)

1️⃣ RequestWrapper

InputStream → 读取 → 同时写入 byte[] cache

👉 类似“旁路复制”


2️⃣ ResponseWrapper

写 OutputStream → 先写到缓存 → 最后再写回真正 response

⚠️ 七、常见坑总结(一定要会)

❌ 1:读取不到 request body

原因:

还没被消费(Controller 还没读)

❌ 2:response 丢失

原因:

没调用 copyBodyToResponse()

❌ 3:大文件内存爆炸

👉 因为:

全部缓存到内存(byte[])

👉 解决:

  • 限制大小
  • 不对大文件启用

❌ 4:只对 body 生效

👉 不能缓存:

  • query 参数
  • header

🚀 八、面试总结(建议背)

👉 ContentCachingRequestWrapper 和 ResponseWrapper 本质是通过包装 ServletRequest/Response,将流数据缓存到内存中,从而解决请求和响应流只能读/写一次的问题,常用于日志记录和审计场景,但需要注意请求是延迟缓存、响应必须手动写回,以及大数据量带来的内存问题。

posted @ 2026-04-13 18:55  LARRY1024  阅读(8)  评论(0)    收藏  举报