Spring Java中如何为ResponseEntity添加Shallow ETag支持
Spring 更简便的Shallow ETag实现方案
你完全不需要自己手写过滤器实现Shallow ETag,Spring Web MVC本身就提供了开箱即用的组件,比你现在每个接口手动计算ETag、手动塞到ResponseEntity里的写法简单很多。
方案1:全局生效,零业务代码侵入
Spring 内置了ShallowEtagHeaderFilter,所有Shallow ETag的生成、校验逻辑都已经封装好了,你只需要把这个过滤器注册到Spring容器里就行,不用写任何核心逻辑。
Spring Boot项目直接加个配置类注册过滤器即可:
@Configuration public class WebConfig { @Bean public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagFilter() { FilterRegistrationBean<ShallowEtagHeaderFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new ShallowEtagHeaderFilter()); registration.addUrlPatterns("/api/*"); // 按需配置需要生效的接口路径,不需要的静态资源路径可以直接排除 return registration; } }
这个过滤器的工作逻辑完全符合Shallow ETag规范:
- 自动缓存响应的输出流内容,基于响应字节内容计算MD5哈希,自动生成带
W/前缀的弱ETag写入响应头 - 自动读取请求头里的
If-None-Match值和生成的ETag比对,匹配的话直接返回304 Not Modified,不会把响应体传输给客户端,节省带宽 - 所有逻辑对业务代码完全透明,你原来接口里手动写的
.eTag(getETag(preloadMap))这行可以直接删掉,直接正常返回响应体即可:
return ResponseEntity.ok().body(preloadMap);
不需要在每个接口重复写ETag计算逻辑。
方案2:细粒度控制,只给指定接口加Shallow ETag
如果不想全局开启过滤器,只想给部分接口加Shallow ETag,可以用Spring自带的ResponseBodyAdvice做统一拦截处理,同样不需要每个接口手动写ETag逻辑。
- 先写一个自定义注解用来标记需要加Shallow ETag的接口:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface EnableShallowEtag { }
- 实现响应拦截逻辑,统一做ETag生成和校验:
@ControllerAdvice public class ShallowEtagAdvice implements ResponseBodyAdvice<Object> { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { // 只给加了@EnableShallowEtag注解的接口生效 return returnType.hasMethodAnnotation(EnableShallowEtag.class); } @Override public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { try { // 序列化响应体计算MD5,生成标准Shallow ETag byte[] bodyBytes = OBJECT_MAPPER.writeValueAsBytes(body); String etag = "W/\"" + DigestUtils.md5DigestAsHex(bodyBytes) + "\""; response.getHeaders().setETag(etag); // 比对请求头,匹配直接返回304 String requestEtag = request.getHeaders().getFirst(HttpHeaders.IF_NONE_MATCH); if (etag.equals(requestEtag)) { response.setStatusCode(HttpStatus.NOT_MODIFIED); return null; } return body; } catch (JsonProcessingException e) { throw new RuntimeException("Shallow ETag计算失败", e); } } }
之后哪个接口需要Shallow ETag支持,直接在接口方法上加@EnableShallowEtag注解就行,业务代码里不需要写任何ETag相关的逻辑,正常返回响应体即可。
注意点
- 内置
ShallowEtagHeaderFilter的性能开销极低,MD5计算的耗时对大部分接口来说可以忽略,生产环境直接用完全没问题。 - 如果你之前的
getETag逻辑不是基于全响应内容计算,而是基于特定业务字段做版本标记,可以在方案2的基础上调整ETag的计算规则,比每个接口手动塞ETag的维护成本低很多。
内容的提问来源于stack exchange,提问作者Charu Garg
相关产品推荐
相关产品推荐

