Spring Boot REST接口HTTP缓存控制关键在于生成Last-Modified或ETag并正确响应304;Last-Modified基于秒级时间戳,适用于有稳定更新时间的资源;ETag基于内容哈希,精度高,适用于JSON等动态响应;需配置etag:true或手动构造ResponseEntity,注意Cache-Control优先级更高且ETag须加英文双引号。

Spring Boot 实现 REST 接口的 HTTP 缓存控制,关键在于让服务器能根据资源状态生成 Last-Modified 或 ETag,并在收到条件请求(If-Modified-Since 或 If-None-Match)时正确响应 304 Not Modified。这不是简单加个注解就能完成的事,而是需要结合资源语义、响应构造和 Spring 的底层机制来配合。
理解 Last-Modified 和 ETag 的适用场景
两者都是“协商缓存”机制,用于验证本地缓存是否仍有效,但设计逻辑不同:
- Last-Modified 基于时间戳,适合有明确、稳定修改时间的资源(如文件系统中的静态资源、数据库记录的更新时间字段)。它精度只到秒,且依赖服务端时间准确。
- ETag 是资源内容的摘要标识(如 MD5/SHA256),更精确可靠。适合内容驱动型资源——哪怕时间没变,只要内容变了,ETag 就变;内容没变,ETag 就不变。对 JSON 响应、动态计算结果等尤其适用。
启用 ETag 自动生成(最常用且推荐)
Spring Boot 默认不开启 ETag,需显式配置。在 application.yml 中添加:
spring:
web:
resources:
cache:
cachecontrol:
max-age: 3600
# 启用 ETag(仅对静态资源生效)
mvc:
# 启用 ETag(对 @ResponseBody 响应也生效)
etag: true该配置会让 Spring MVC 对所有 @ResponseBody 方法返回的响应自动计算并设置 ETag 头(基于响应体字节流的 MD5)。注意:它适用于纯数据接口(如返回 JSON),但不适用于含动态时间戳、随机值或用户上下文的响应——否则每次 ETag 都不同,缓存失效。
手动控制 Last-Modified 或 ETag(精准可控)
当自动机制不够用时,推荐在 Controller 中手动处理。以返回用户信息为例:
- 获取资源最后更新时间(如从数据库查
updated_at字段),用ResponseEntity构造带Last-Modified的响应:
@GetMapping("/user/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id, HttpServletRequest request) {
User user = userService.findById(id);
long lastModified = user.getUpdatedAt().toInstant().toEpochMilli();
<pre class="brush:php;toolbar:false;">// 检查 If-Modified-Since
long ifModifiedSince = request.getDateHeader("If-Modified-Since");
if (ifModifiedSince != -1 && lastModified <= ifModifiedSince + 1000) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}
return ResponseEntity.ok()
.lastModified(lastModified)
.body(user);}
- 若想用 ETag(比如基于 JSON 序列化后的哈希),可手动计算:
String json = objectMapper.writeValueAsString(user);
String etag = "\"" + DigestUtils.md5Hex(json) + "\"";
if (request.getHeader("If-None-Match") != null &&
request.getHeader("If-None-Match").equals(etag)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}
return ResponseEntity.ok()
.eTag(etag)
.body(user);注意事项与常见陷阱
实际落地时容易忽略几个关键点:
-
Cache-Control优先级高于Last-Modified/ETag。如果设置了max-age=3600,浏览器在 1 小时内根本不会发条件请求——所以协商缓存只在强缓存过期后才起作用。 - 手动设置
Last-Modified时,务必确保时间单位是毫秒(System.currentTimeMillis()或Instant.toEpochMilli()),否则可能被忽略。 - ETag 值必须用英文双引号包裹(如
"abc123"),否则不符合 HTTP 规范,浏览器可能不识别。 - 不要对 POST/PUT 等非幂等接口启用 ETag/Last-Modified,因为这类请求本就不该被缓存。


















