响应数据需在写入前缓存加工,应继承HttpServletResponseWrapper重写getWriter()和getOutputStream()指向内存缓冲区,按编码一致缓存;JSON响应可合并request中增强数据并配置化字段;大文件或流式响应须跳过缓存防OOM;加工前resetBuffer,禁用ETag等敏感头以保安全。

在模块开发进阶中,过滤链(Filter Chain)不仅是请求拦截的常用机制,也能用于响应阶段的数据聚合与再处理。关键在于:响应数据通常以流形式写出,不可重复读取,因此“累加”和“二次加工”必须在写入前完成——常见做法是用 HttpServletResponseWrapper 拦截并缓存输出内容,再统一处理。
用 ResponseWrapper 缓存原始响应体
标准 Servlet 过滤器无法直接修改已写出的响应体。需继承 HttpServletResponseWrapper,重写 getWriter() 和 getOutputStream(),将输出导向内存缓冲区(如 ByteArrayOutputStream 或 StringWriter)。注意字符编码需与 response.getCharacterEncoding() 保持一致,否则中文可能乱码。
- 创建自定义 Wrapper 类,内部持有一个 ByteArrayOutputStream 缓存字节流
- 重写 getOutputStream() 返回包装后的 ServletOutputStream
- 重写 getWriter() 返回包装后的 PrintWriter,底层仍写入同一缓冲区
- 在 doFilter() 后调用 flushBuffer() 确保所有内容已写入缓冲区
在过滤链末尾统一提取、累加与转换
多个过滤器可能各自生成片段数据(如模板头、业务JSON、统计脚注),可通过 ThreadLocal 或 request.setAttribute() 在链中传递中间结果。但更稳妥的方式是:只让最后一个过滤器负责“汇总”,其他过滤器仅标记意图或注入结构化数据(如 Map
- 前置过滤器将待追加字段存入 request.setAttribute("response_enhance", map)
- Wrapper 获取原始响应体后,解析为 JSON(若 Content-Type 是 application/json)
- 合并 request 中的增强数据,例如添加 "timestamp"、"version"、"total" 等字段
- 支持配置化字段映射,避免硬编码,比如通过 @WebFilter 注解参数指定增强键名
处理流式响应与大体积数据的边界情况
不是所有响应都适合全量缓存。对于文件下载、视频流或超大报表,应识别 Content-Disposition、Content-Type 或响应大小阈值(如 >1MB),自动跳过包装逻辑,避免 OOM 或延迟。可结合 ContentCachingResponseWrapper(Spring 提供)简化实现,但它默认不缓存超过 10KB 的响应体,需显式设置 setContentLength() 或调整 setBufferSize()。
- 检查 response.getContentType() 是否含 "application/octet-stream" 或 "video/"
- 在 doFilter 前判断 Content-Length 已设且过大,或启用 “流式白名单” 模式
- 对 JSON API 响应强制启用缓存;对静态资源响应直接放行
- 记录被跳过的响应类型与原因,便于后期审计与优化
安全与兼容性注意事项
二次加工可能破坏签名、ETag、压缩或 HTTPS 完整性校验。若原始响应已启用 GZIP,Wrapper 必须在解压后操作,再重新压缩——这需要额外的流处理能力。更推荐的做法是:只在未压缩、非签名场景下启用加工逻辑,并在响应头中清除 Vary: Accept-Encoding 和 ETag,避免缓存污染。
- 加工前调用 response.resetBuffer() 清除已写入的 header 和 body
- 加工完成后手动设置 Content-Length(尤其禁用 chunked 时)
- 避免修改 Set-Cookie、WWW-Authenticate 等敏感头字段
- 在测试环境开启响应体 diff 日志,比对加工前后 JSON 结构一致性










