Spring HTTP缓存应用场景举例
目录
- 一、什么是 HTTP 缓存?
- 核心原理:Cache-Control+ 条件请求(ETag / Last-Modified)
- 二、CacheControl类:简化 Cache-Control 配置
- 示例代码解析:GGwNl
- 三、在控制器中使用 HTTP 缓存(Controllers)
- ✅ 推荐做法:返回ResponseEntity并添加 ETag 和 Cache-Control
- 发生了什么?
- ⚙️ 手动检查条件请求(更灵活控制)
- checkNotModified()做了什么?
- 四、静态资源缓存(Static Resources)
- 五、Shallow ETag Filter(浅层 ETag 过滤器)
- 问题引入:
- 特点:
- 使用方式(web.XML 或 Java Config 添加过滤器):
- ✅ 总结:如何理解并运用 HTTP 缓存?
- 实际应用场景举例
- 场景1:图书详情页 API
- 场景2:首页 html(不希望缓存)
- 学习建议
以下内容是来自 Spring Framework 官方文档 中关于 HTTP 缓存(HTTP Caching) 的部分,特别是针对 WebFlux 和 Web MVC 框架的说明。下面我将用通俗易懂的方式为你逐段解析,并帮助你理解其核心概念和实际应用。
一、什么是 HTTP 缓存?
原文开头:
HTTP caching can significantly improve the performance of a web application.
HTTP 缓存是一种优化机制,它的目标是:
让浏览器或中间代理服务器(如 CDN)避免重复下载相同的资源,从而:- 减少网络请求
- 节省带宽
- 提升页面加载速度
- 降低后端服务器压力
核心原理:Cache-Control+ 条件请求(ETag / Last-Modified)
Cache-Control头- 控制资源可以被谁缓存、缓存多久。
- 例如:
Cache-Control: max-age=3600表示“这个响应可以缓存 1 小时”。
- 条件请求头(Conditional Request Headers)
- 当缓存过期或需要验证是否更新时,浏览器会带上:
- 如果内容没变,服务器就返回 304 Not Modified(不传正文),节省传输开销。
If-None-Match: 对应服务器之前返回的 ETag
If-Modified-Since: 对应之前的 Last-Modified
二、CacheControl类:简化 Cache-Control 配置
Spring 提供了一个工具类 CacheControl,让你不用手动拼字符串设置 Cache-Control 头。
示例代码解析:
// 缓存1小时:"Cache-Control: max-age=3600"
CacheControl.maxAge(1, TimeUnit.HOURS);
// 禁止缓存:"Cache-Control: no-store"
CacheControl.noStore();
// 自定义策略
CacheControl.maxAge(10, TimeUnit.DAYS)
.noTransform()
.cachePublic();
// 结果:"max-age=864000, public, no-transform"
| 方法 | 含义 |
|---|---|
maxAge(...) | 设置最大缓存时间(秒数) |
noStore() | 禁止任何缓存(敏感数据常用) |
cachePublic() | 允许公共缓存(如 CDN)缓存 |
cachePrivate() | 只允许私有缓存(如浏览器) |
noTransform() | 防止中间代理压缩或转换内容 |
这些方法链式调用,语义清晰,比直接写 header 更安全、易读。
三、在控制器中使用 HTTP 缓存(Controllers)
这是最常见也最重要的场景 —— 给动态接口加上缓存支持。
✅ 推荐做法:返回ResponseEntity并添加 ETag 和 Cache-Control
@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {
Book book = findBook(id);
String version = book.getVersion(); // 如数据库版本号、hash值等
return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS)) // 缓存30天
.eTag(version) // 设置ETag
.body(book);
}
发生了什么?
- 第一次请求:
- 返回状态码
200 OK - 响应头包含:
ETag: "abc123" Cache-Control: max-age=2592000
- 浏览器下次请求同一 URL:
- 自动带上:
If-None-Match: "abc123"
- Spring 检查发现 ETag 相同 → 返回
304 Not Modified(空 body)
浏览器直接使用本地缓存!
✅ 效果:节省流量,提升用户体验。
⚙️ 手动检查条件请求(更灵活控制)
有时候你想先判断是否需要处理业务逻辑:
@RequestMapping
public String myHandleMethod(WebRequest request, Model model) {
long eTag = bookService.getCurrentVersion(); // 应用级计算ETag
if (request.checkNotModified(eTag)) {
return null; // 不再执行后续逻辑,自动返回304
}
model.addAttribute("data", ...);
return "myViewName";
}
checkNotModified()做了什么?
- 比较客户端发来的
If-None-Match或If-Modified-Since是否匹配。 - 匹配 → 设置响应为
304 Not Modified,并终止处理 → 返回null - 不匹配 → 正常继续处理请求
注意:
- 对于
GET/HEAD请求,返回304 - 对于
POST/PUT/DELETE请求,若条件不满足,应返回412 Precondition Failed(防止并发修改)
四、静态资源缓存(Static Resources)
比如 js、css、图片等文件,非常适合长期缓存。
建议配置:
- 设置长时间的
Cache-Control: max-age=31536000(一年) - 使用内容指纹命名(如
app.a1b2c3.js),确保更新后 URL 改变,打破缓存
这样既能充分利用缓存,又能保证更新生效。
文档提示参考 “Configuring Static Resources” 进行详细配置(通常通过 Spring Boot 的
spring.web.resources.cache配置项实现)
五、Shallow EandroidTag Filter(浅层 ETwww.devze.comag 过滤器)
问题引入:
上面我们手动设置了 ETag,但如果你不想自己管理 ETag 怎么办?
ShallowEtagHeaderFilter
特点:
| 项目 | Shallow ETag | 手动 ETag |
|---|---|---|
| 计算方式 | 基于响应体内容生成 hash | 开发者根据业务决定(如版本号) |
| 是否节省 CPU | ❌ 仍需完整生成响应内容 | ✅ 可提前中断(未改就不生成) |
| 是否节省带宽 | ✅ 若相同则返回 304 | ✅ |
| 性能影响 | 高负载下可能增加 CPU 使用率 | 更高效(可短路) |
使用方式(web.xml 或 Java Config 添加过滤器):
<filter>
<filter-name>etagFilter</filter-name>
<filter-class>org.springframework.web.filter.ShallowEtagHeaderFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>etagFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
⚠️ 注意:它只对 GET 请求有效,且必须等到整个响应生成完才计算 ETag,所以不能减少服务器计算量,只能减少传输量。
✅ 总结:如何理解并运用 HTTP 缓存?
| 层面 | 建议做法 |
|---|---|
| 通用原则 | 利用 Cache-Control 控制缓存策略,用 ETag/Last-Modified 实现条件请求 |
| 动态接口 | 在 Controller 返回 ResponseEntity,设置 .eTag() 和 .cacheControl() |
| 静态资源 | 设置长期 max-age,配合文件名哈希 |
| 不想手动js管ETag? | 用 ShallowEtagHeaderFilter 自动生成(牺牲CPU换带宽) |
| 想极致性能? | 手动计算 ETag 并调用 request.checkNotModified() 提前退出 |
实际应用场景举例
场景1:图书详情页 API
@GetMapping("/api/books/{id}")
public ResponseEntity<Book> getBook(@PathVariable Long id, WebRequest request) {
Book book = bookService.findById(id);
Stringjavascript etag = book.getUpdatedAt().toString(); // 或 MD5(content)
if (request.checkNotModified(etag)) {
return null;
}
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
.eTag(etag)
.body(book);
}
效果:
- 内容未变 → 返回 304,极快响应
- 内容变了 → 返回 200 + 新数据
场景2:首页 HTML(不希望缓存)
@GetMapping("/")
public String home(Model model, WebRequest request) {
String etag = System.currentTimeMillis() / 3600 + ""; // 每小时变化
if (request.checkNotModified(etag)) {
return null;
}
model.addAttribute("time", new Date());
return "home";
}
搭配 Cache-Control: max-age=3600,实现每小时最多重新生成一次。
学习建议
- 动手实验:用 Postman 或浏览器开发者工具观察请求头的变化。
- 查看 Network Tab:注意
200vs304的区别,以及请求/响应头中的ETag,If-None-Match,Cache-Control。 - 结合 Spring Boot:使用
application.yml配置静态资源缓存:
spring:
web:
resources:
cache:
cachecontrol:
max-age: 1h
如有具体需求(比如“我想让某个接口缓存一天”,或“为什么我的 ETag 没生效?”),欢迎继续提问!
到此这篇关于Spring HTTP缓存全解析的文章就介绍到这了,更多相关Spring HTTP缓存内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
加载中,请稍侯......
精彩评论