1. 引言
本文将探讨如何在Spring Boot过滤器中从ServletResponse
获取响应体。我们将通过缓存响应体的方式解决这个问题,确保在过滤器中能够访问到响应内容。下面开始详细说明。
2. 问题分析
在Spring Boot过滤器中获取响应体是个经典难题。核心原因在于:响应体是在过滤器链执行完成后才写入输出流的,导致在过滤器中无法直接读取。
但某些场景(如生成哈希签名)需要在发送给客户端前获取响应体内容。这就需要我们找到一种方法来拦截并缓存响应体。
3. 使用ContentCachingResponseWrapper
解决方案
Spring提供的ContentCachingResponseWrapper
类能完美解决这个问题。下面是具体实现:
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
ContentCachingResponseWrapper responseCacheWrapperObject =
new ContentCachingResponseWrapper((HttpServletResponse) servletResponse);
filterChain.doFilter(servletRequest, responseCacheWrapperObject);
byte[] responseBody = responseCacheWrapperObject.getContentAsByteArray();
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
byte[] md5Hash = md5Digest.digest(responseBody);
String md5HashString = DatatypeConverter.printHexBinary(md5Hash);
responseCacheWrapperObject.getResponse().setHeader("Response-Body-MD5", md5HashString);
// ...
}
关键点解析:
- ✅ 包装响应对象:使用
ContentCachingResponseWrapper
包装原始响应 - ✅ **必须调用
doFilter()
**:否则请求无法继续传递,违反Servlet规范 - ✅ **必须使用包装对象调用
doFilter()
**:否则缓存机制失效 - ⚠️ 响应体获取:
getContentAsByteArray()
在doFilter()
执行后可用 - ⚠️ 最终步骤:退出前必须调用
copyBodyToResponse()
将响应写回原始对象
responseCacheWrapperObject.copyBodyToResponse(); // 关键步骤!
踩坑提醒:忘记调用copyBodyToResponse()
会导致客户端收到不完整的响应!
4. 过滤器配置
通过FilterRegistrationBean
将过滤器注册到Spring Boot:
@Bean
public FilterRegistrationBean loggingFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MD5Filter());
return registrationBean;
}
简单粗暴的配置方式,直接注入自定义过滤器实例即可。
5. MD5功能测试
使用Spring集成测试验证功能:
@Test
void whenExampleApiCallThenResponseHasMd5Header() throws Exception {
String endpoint = "/api/example";
String expectedResponse = "Hello, World!";
String expectedMD5 = getMD5Hash(expectedResponse);
MvcResult mvcResult = mockMvc.perform(get(endpoint).accept(MediaType.TEXT_PLAIN_VALUE))
.andExpect(status().isOk())
.andReturn();
String md5Header = mvcResult.getResponse()
.getHeader("Response-Body-MD5");
assertThat(md5Header).isEqualTo(expectedMD5);
}
测试辅助方法:
private String getMD5Hash(String input) throws NoSuchAlgorithmException {
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
byte[] md5Hash = md5Digest.digest(input.getBytes(StandardCharsets.UTF_8));
return DatatypeConverter.printHexBinary(md5Hash);
}
测试要点:
- ✅ 调用返回"Hello, World!"的接口
- ✅ 验证响应头包含正确的MD5值
- ✅ 使用与过滤器相同的哈希算法确保一致性
6. 总结
本文通过ContentCachingResponseWrapper
解决了Spring Boot过滤器中获取响应体的难题。核心思路是:
- 包装响应对象实现缓存
- 在过滤器链执行后获取响应体
- 处理完成后将响应写回原始对象
完整代码示例可在GitHub仓库的spring-boot-basic-customization-3
模块中找到。