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);
    // ...
}

关键点解析:

  1. ✅ 包装响应对象:使用ContentCachingResponseWrapper包装原始响应
  2. ✅ **必须调用doFilter()**:否则请求无法继续传递,违反Servlet规范
  3. ✅ **必须使用包装对象调用doFilter()**:否则缓存机制失效
  4. ⚠️ 响应体获取:getContentAsByteArray()doFilter()执行后可用
  5. ⚠️ 最终步骤:退出前必须调用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);
}

测试要点:

  1. ✅ 调用返回"Hello, World!"的接口
  2. ✅ 验证响应头包含正确的MD5值
  3. ✅ 使用与过滤器相同的哈希算法确保一致性

6. 总结

本文通过ContentCachingResponseWrapper解决了Spring Boot过滤器中获取响应体的难题。核心思路是:

  1. 包装响应对象实现缓存
  2. 在过滤器链执行后获取响应体
  3. 处理完成后将响应写回原始对象

完整代码示例可在GitHub仓库的spring-boot-basic-customization-3模块中找到。


原始标题:Get the Response Body in Spring Boot Filter | Baeldung