引言

HTTP缓存是Web开发中提升性能的重要手段,通过合理使用缓存机制,可以显著减少网络请求,降低服务器负载,提升用户体验。本文将深入探讨HTTP缓存的分类、实现原理,并通过Java Spring Boot实战代码演示如何在项目中应用HTTP缓存机制。

HTTP缓存概述

什么是HTTP缓存

HTTP缓存是指浏览器或代理服务器将Web资源(HTML、CSS、JS、图片等)存储在本地,当再次请求相同资源时,直接从本地缓存中获取,而不需要重新从服务器下载。

缓存的好处

  • 提升性能:减少网络请求,加快页面加载速度
  • 降低服务器负载:减少服务器处理请求的压力
  • 节省带宽:减少网络传输的数据量
  • 改善用户体验:页面响应更快,用户体验更佳

HTTP缓存分类

HTTP缓存主要分为两类:

  1. 强缓存:浏览器直接从本地缓存中获取资源,不发送请求到服务器
  2. 协商缓存:浏览器发送请求到服务器,服务器判断资源是否更新,决定返回304还是新资源

强缓存机制

1. Cache-Control响应头

Cache-Control是HTTP/1.1中最重要的缓存控制头,可以设置多种缓存策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@RestController
@RequestMapping("/api/cache")
public class CacheController {

// 设置强缓存 - max-age
@GetMapping("/strong-cache")
public ResponseEntity<String> strongCache() {
String content = "这是强缓存内容,缓存时间180秒";

HttpHeaders headers = new HttpHeaders();
// 设置缓存时间为180秒
headers.setCacheControl("max-age=180");

return ResponseEntity.ok()
.headers(headers)
.body(content);
}

// 设置强缓存 - public
@GetMapping("/public-cache")
public ResponseEntity<String> publicCache() {
String content = "这是公共缓存内容,可以被任何缓存存储";

HttpHeaders headers = new HttpHeaders();
// 公共缓存,任何缓存都可以存储
headers.setCacheControl("public, max-age=3600");

return ResponseEntity.ok()
.headers(headers)
.body(content);
}

// 设置强缓存 - private
@GetMapping("/private-cache")
public ResponseEntity<String> privateCache() {
String content = "这是私有缓存内容,只能被浏览器缓存";

HttpHeaders headers = new HttpHeaders();
// 私有缓存,只能被浏览器缓存
headers.setCacheControl("private, max-age=300");

return ResponseEntity.ok()
.headers(headers)
.body(content);
}

// 设置强缓存 - no-cache
@GetMapping("/no-cache")
public ResponseEntity<String> noCache() {
String content = "这是no-cache内容,需要验证缓存";

HttpHeaders headers = new HttpHeaders();
// no-cache表示需要验证缓存
headers.setCacheControl("no-cache");

return ResponseEntity.ok()
.headers(headers)
.body(content);
}

// 设置强缓存 - no-store
@GetMapping("/no-store")
public ResponseEntity<String> noStore() {
String content = "这是no-store内容,不允许缓存";

HttpHeaders headers = new HttpHeaders();
// no-store表示不允许任何缓存存储
headers.setCacheControl("no-store");

return ResponseEntity.ok()
.headers(headers)
.body(content);
}
}

2. Expires响应头

Expires是HTTP/1.0中的缓存控制头,指定资源的过期时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@RestController
@RequestMapping("/api/expires")
public class ExpiresController {

@GetMapping("/expires-cache")
public ResponseEntity<String> expiresCache() {
String content = "这是Expires缓存内容";

HttpHeaders headers = new HttpHeaders();

// 设置过期时间为1小时后
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR, 1);
Date expiresDate = calendar.getTime();

// 使用SimpleDateFormat格式化日期
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
headers.set("Expires", sdf.format(expiresDate));

return ResponseEntity.ok()
.headers(headers)
.body(content);
}

// 动态设置过期时间
@GetMapping("/dynamic-expires")
public ResponseEntity<String> dynamicExpires(@RequestParam(defaultValue = "300") int seconds) {
String content = "这是动态过期时间缓存内容,过期时间:" + seconds + "秒";

HttpHeaders headers = new HttpHeaders();

// 动态设置过期时间
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, seconds);
Date expiresDate = calendar.getTime();

SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
headers.set("Expires", sdf.format(expiresDate));

return ResponseEntity.ok()
.headers(headers)
.body(content);
}
}

3. 强缓存配置类

创建一个统一的缓存配置类来管理强缓存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Configuration
public class CacheConfig {

// 静态资源缓存配置
@Bean
public WebMvcConfigurer webMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 静态资源缓存配置
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(30)));

// 图片资源缓存配置
registry.addResourceHandler("/images/**")
.addResourceLocations("classpath:/static/images/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(7)));

// CSS和JS文件缓存配置
registry.addResourceHandler("/css/**", "/js/**")
.addResourceLocations("classpath:/static/css/", "classpath:/static/js/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(1)));
}
};
}

// 缓存拦截器
@Bean
public HandlerInterceptor cacheInterceptor() {
return new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 为API请求设置默认缓存策略
if (request.getRequestURI().startsWith("/api/")) {
response.setHeader("Cache-Control", "no-cache, must-revalidate");
}
return true;
}
};
}
}

协商缓存机制

1. Last-Modified实现

Last-Modified是协商缓存的一种实现方式,通过比较资源的最后修改时间来判断是否需要更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
@RestController
@RequestMapping("/api/last-modified")
public class LastModifiedController {

@GetMapping("/file")
public ResponseEntity<String> getFile(HttpServletRequest request) {
String content = "这是文件内容,最后修改时间:" + new Date();

// 模拟文件的最后修改时间
long lastModified = System.currentTimeMillis() - 3600000; // 1小时前
Date lastModifiedDate = new Date(lastModified);

// 获取请求头中的If-Modified-Since
String ifModifiedSince = request.getHeader("If-Modified-Since");

if (ifModifiedSince != null) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
Date clientLastModified = sdf.parse(ifModifiedSince);

// 比较最后修改时间
if (clientLastModified.getTime() >= lastModified) {
// 资源未修改,返回304
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}
} catch (ParseException e) {
// 解析失败,返回新资源
}
}

// 设置Last-Modified头
HttpHeaders headers = new HttpHeaders();
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
headers.set("Last-Modified", sdf.format(lastModifiedDate));

return ResponseEntity.ok()
.headers(headers)
.body(content);
}

// 动态文件内容
@GetMapping("/dynamic-file")
public ResponseEntity<String> getDynamicFile(HttpServletRequest request) {
// 模拟动态生成的文件内容
String content = "动态文件内容,生成时间:" + new Date();

// 获取文件的实际修改时间(这里用当前时间模拟)
long lastModified = System.currentTimeMillis();
Date lastModifiedDate = new Date(lastModified);

String ifModifiedSince = request.getHeader("If-Modified-Since");

if (ifModifiedSince != null) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
Date clientLastModified = sdf.parse(ifModifiedSince);

// 如果客户端缓存时间大于等于服务器时间,返回304
if (clientLastModified.getTime() >= lastModified) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}
} catch (ParseException e) {
// 解析失败,返回新资源
}
}

HttpHeaders headers = new HttpHeaders();
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
headers.set("Last-Modified", sdf.format(lastModifiedDate));

return ResponseEntity.ok()
.headers(headers)
.body(content);
}
}

2. ETag实现

ETag是协商缓存的另一种实现方式,通过比较资源的唯一标识符来判断是否需要更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
@RestController
@RequestMapping("/api/etag")
public class ETagController {

@GetMapping("/resource")
public ResponseEntity<String> getResource(HttpServletRequest request) {
String content = "这是ETag资源内容";

// 生成ETag(基于内容生成)
String etag = generateETag(content);

// 获取请求头中的If-None-Match
String ifNoneMatch = request.getHeader("If-None-Match");

if (ifNoneMatch != null && ifNoneMatch.equals(etag)) {
// ETag匹配,返回304
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}

// 设置ETag头
HttpHeaders headers = new HttpHeaders();
headers.set("ETag", etag);

return ResponseEntity.ok()
.headers(headers)
.body(content);
}

// 动态ETag生成
@GetMapping("/dynamic-resource")
public ResponseEntity<String> getDynamicResource(HttpServletRequest request) {
// 模拟动态内容
String content = "动态ETag资源内容,时间戳:" + System.currentTimeMillis();

// 基于内容生成ETag
String etag = generateETag(content);

String ifNoneMatch = request.getHeader("If-None-Match");

if (ifNoneMatch != null && ifNoneMatch.equals(etag)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}

HttpHeaders headers = new HttpHeaders();
headers.set("ETag", etag);

return ResponseEntity.ok()
.headers(headers)
.body(content);
}

// 弱ETag实现
@GetMapping("/weak-etag")
public ResponseEntity<String> getWeakETagResource(HttpServletRequest request) {
String content = "这是弱ETag资源内容";

// 生成弱ETag
String weakETag = "W/\"" + generateETag(content) + "\"";

String ifNoneMatch = request.getHeader("If-None-Match");

if (ifNoneMatch != null && ifNoneMatch.equals(weakETag)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}

HttpHeaders headers = new HttpHeaders();
headers.set("ETag", weakETag);

return ResponseEntity.ok()
.headers(headers)
.body(content);
}

// 生成ETag的方法
private String generateETag(String content) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(content.getBytes());
StringBuilder hexString = new StringBuilder();

for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}

return "\"" + hexString.toString() + "\"";
} catch (NoSuchAlgorithmException e) {
// 如果MD5不可用,使用简单的hash
return "\"" + content.hashCode() + "\"";
}
}
}

3. 协商缓存服务类

创建一个专门处理协商缓存的服务类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@Service
public class CacheService {

// 检查Last-Modified缓存
public boolean checkLastModifiedCache(HttpServletRequest request, long lastModified) {
String ifModifiedSince = request.getHeader("If-Modified-Since");

if (ifModifiedSince != null) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
Date clientLastModified = sdf.parse(ifModifiedSince);

return clientLastModified.getTime() >= lastModified;
} catch (ParseException e) {
return false;
}
}

return false;
}

// 检查ETag缓存
public boolean checkETagCache(HttpServletRequest request, String etag) {
String ifNoneMatch = request.getHeader("If-None-Match");
return ifNoneMatch != null && ifNoneMatch.equals(etag);
}

// 设置Last-Modified响应头
public void setLastModifiedHeader(HttpServletResponse response, long lastModified) {
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
response.setHeader("Last-Modified", sdf.format(new Date(lastModified)));
}

// 设置ETag响应头
public void setETagHeader(HttpServletResponse response, String etag) {
response.setHeader("ETag", etag);
}

// 生成ETag
public String generateETag(String content) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] hash = md.digest(content.getBytes());
StringBuilder hexString = new StringBuilder();

for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}

return "\"" + hexString.toString() + "\"";
} catch (NoSuchAlgorithmException e) {
return "\"" + content.hashCode() + "\"";
}
}

// 生成弱ETag
public String generateWeakETag(String content) {
return "W/" + generateETag(content);
}
}

Spring Boot缓存集成

1. 缓存配置

在Spring Boot中集成HTTP缓存功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Configuration
@EnableWebMvc
public class WebCacheConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加缓存拦截器
registry.addInterceptor(new CacheInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/no-cache/**");
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 静态资源缓存配置
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(30)));

registry.addResourceHandler("/images/**")
.addResourceLocations("classpath:/static/images/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(7)));
}

// 缓存拦截器
public static class CacheInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 为API请求设置默认缓存策略
if (request.getRequestURI().startsWith("/api/")) {
response.setHeader("Cache-Control", "no-cache, must-revalidate");
}
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
// 后处理逻辑
}
}
}

2. 缓存注解支持

创建自定义的缓存注解来简化缓存配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 自定义缓存注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface HttpCache {

// 缓存时间(秒)
int maxAge() default 300;

// 缓存类型
CacheType type() default CacheType.STRONG;

// 是否公开缓存
boolean isPublic() default false;

// 是否私有缓存
boolean isPrivate() default false;

// 是否允许缓存
boolean noCache() default false;

// 是否不允许存储
boolean noStore() default false;

enum CacheType {
STRONG, // 强缓存
NEGOTIATE, // 协商缓存
MIXED // 混合缓存
}
}

// 缓存注解处理器
@Component
public class HttpCacheProcessor {

public void processCache(HttpServletResponse response, HttpCache httpCache) {
StringBuilder cacheControl = new StringBuilder();

if (httpCache.noStore()) {
cacheControl.append("no-store");
} else if (httpCache.noCache()) {
cacheControl.append("no-cache");
} else {
if (httpCache.isPublic()) {
cacheControl.append("public");
} else if (httpCache.isPrivate()) {
cacheControl.append("private");
}

if (httpCache.maxAge() > 0) {
if (cacheControl.length() > 0) {
cacheControl.append(", ");
}
cacheControl.append("max-age=").append(httpCache.maxAge());
}
}

response.setHeader("Cache-Control", cacheControl.toString());
}
}

// 使用缓存注解的控制器
@RestController
@RequestMapping("/api/annotated-cache")
public class AnnotatedCacheController {

@Autowired
private HttpCacheProcessor cacheProcessor;

@HttpCache(maxAge = 300, isPublic = true)
@GetMapping("/public-cache")
public ResponseEntity<String> publicCache(HttpServletResponse response) {
String content = "这是使用注解配置的公共缓存";
return ResponseEntity.ok().body(content);
}

@HttpCache(maxAge = 600, isPrivate = true)
@GetMapping("/private-cache")
public ResponseEntity<String> privateCache(HttpServletResponse response) {
String content = "这是使用注解配置的私有缓存";
return ResponseEntity.ok().body(content);
}

@HttpCache(noCache = true)
@GetMapping("/no-cache")
public ResponseEntity<String> noCache(HttpServletResponse response) {
String content = "这是使用注解配置的no-cache";
return ResponseEntity.ok().body(content);
}
}

3. 缓存过滤器

创建一个缓存过滤器来处理全局缓存逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@Component
@Order(1)
public class CacheFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;

// 设置默认缓存策略
setDefaultCacheHeaders(httpRequest, httpResponse);

// 继续处理请求
chain.doFilter(request, response);

// 后处理缓存头
postProcessCacheHeaders(httpRequest, httpResponse);
}

private void setDefaultCacheHeaders(HttpServletRequest request, HttpServletResponse response) {
String requestURI = request.getRequestURI();

// 根据请求路径设置不同的缓存策略
if (requestURI.startsWith("/static/")) {
response.setHeader("Cache-Control", "public, max-age=31536000"); // 1年
} else if (requestURI.startsWith("/api/")) {
response.setHeader("Cache-Control", "no-cache, must-revalidate");
} else if (requestURI.startsWith("/images/")) {
response.setHeader("Cache-Control", "public, max-age=604800"); // 1周
}
}

private void postProcessCacheHeaders(HttpServletRequest request, HttpServletResponse response) {
// 后处理逻辑,可以添加额外的缓存头
String requestURI = request.getRequestURI();

if (requestURI.startsWith("/api/")) {
// 为API请求添加ETag
String content = response.getHeader("Content-Length");
if (content != null) {
String etag = generateETag(content);
response.setHeader("ETag", etag);
}
}
}

private String generateETag(String content) {
return "\"" + content.hashCode() + "\"";
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化逻辑
}

@Override
public void destroy() {
// 销毁逻辑
}
}

实际项目应用案例

1. 电商系统缓存应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
@RestController
@RequestMapping("/api/ecommerce")
public class EcommerceCacheController {

@Autowired
private ProductService productService;

@Autowired
private CacheService cacheService;

// 商品详情页缓存
@GetMapping("/product/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id, HttpServletRequest request) {
Product product = productService.getProductById(id);

if (product == null) {
return ResponseEntity.notFound().build();
}

// 生成ETag
String etag = cacheService.generateETag(product.toString());

// 检查ETag缓存
if (cacheService.checkETagCache(request, etag)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}

// 设置缓存头
HttpHeaders headers = new HttpHeaders();
headers.set("ETag", etag);
headers.set("Cache-Control", "public, max-age=3600"); // 1小时

return ResponseEntity.ok()
.headers(headers)
.body(product);
}

// 商品列表缓存
@GetMapping("/products")
public ResponseEntity<List<Product>> getProducts(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
HttpServletRequest request) {

List<Product> products = productService.getProducts(page, size);

// 生成ETag
String etag = cacheService.generateETag(products.toString());

if (cacheService.checkETagCache(request, etag)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}

HttpHeaders headers = new HttpHeaders();
headers.set("ETag", etag);
headers.set("Cache-Control", "public, max-age=1800"); // 30分钟

return ResponseEntity.ok()
.headers(headers)
.body(products);
}

// 用户购物车缓存
@GetMapping("/cart/{userId}")
public ResponseEntity<Cart> getCart(@PathVariable Long userId, HttpServletRequest request) {
Cart cart = productService.getCartByUserId(userId);

if (cart == null) {
return ResponseEntity.notFound().build();
}

String etag = cacheService.generateETag(cart.toString());

if (cacheService.checkETagCache(request, etag)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}

HttpHeaders headers = new HttpHeaders();
headers.set("ETag", etag);
headers.set("Cache-Control", "private, max-age=300"); // 5分钟,私有缓存

return ResponseEntity.ok()
.headers(headers)
.body(cart);
}
}

2. 内容管理系统缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@RestController
@RequestMapping("/api/cms")
public class CmsCacheController {

@Autowired
private ArticleService articleService;

@Autowired
private CacheService cacheService;

// 文章内容缓存
@GetMapping("/article/{id}")
public ResponseEntity<Article> getArticle(@PathVariable Long id, HttpServletRequest request) {
Article article = articleService.getArticleById(id);

if (article == null) {
return ResponseEntity.notFound().build();
}

// 使用Last-Modified缓存
long lastModified = article.getUpdateTime().getTime();

if (cacheService.checkLastModifiedCache(request, lastModified)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}

HttpHeaders headers = new HttpHeaders();
cacheService.setLastModifiedHeader((HttpServletResponse) request.getAttribute("response"), lastModified);
headers.set("Cache-Control", "public, max-age=7200"); // 2小时

return ResponseEntity.ok()
.headers(headers)
.body(article);
}

// 文章列表缓存
@GetMapping("/articles")
public ResponseEntity<List<Article>> getArticles(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String category,
HttpServletRequest request) {

List<Article> articles = articleService.getArticles(page, size, category);

// 生成ETag
String etag = cacheService.generateETag(articles.toString());

if (cacheService.checkETagCache(request, etag)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}

HttpHeaders headers = new HttpHeaders();
headers.set("ETag", etag);
headers.set("Cache-Control", "public, max-age=1800"); // 30分钟

return ResponseEntity.ok()
.headers(headers)
.body(articles);
}

// 静态资源缓存
@GetMapping("/static/{filename}")
public ResponseEntity<Resource> getStaticResource(@PathVariable String filename, HttpServletRequest request) {
Resource resource = articleService.getStaticResource(filename);

if (!resource.exists()) {
return ResponseEntity.notFound().build();
}

try {
long lastModified = resource.lastModified();

if (cacheService.checkLastModifiedCache(request, lastModified)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}

HttpHeaders headers = new HttpHeaders();
cacheService.setLastModifiedHeader((HttpServletResponse) request.getAttribute("response"), lastModified);
headers.set("Cache-Control", "public, max-age=31536000"); // 1年

return ResponseEntity.ok()
.headers(headers)
.body(resource);
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
}

3. 缓存监控和管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
@RestController
@RequestMapping("/api/cache-management")
public class CacheManagementController {

@Autowired
private CacheService cacheService;

// 缓存统计信息
@GetMapping("/stats")
public ResponseEntity<CacheStats> getCacheStats() {
CacheStats stats = new CacheStats();

// 模拟统计信息
stats.setTotalRequests(1000);
stats.setCacheHits(800);
stats.setCacheMisses(200);
stats.setHitRate(0.8);

return ResponseEntity.ok(stats);
}

// 清除特定缓存
@PostMapping("/clear/{type}")
public ResponseEntity<String> clearCache(@PathVariable String type) {
// 清除特定类型的缓存
switch (type) {
case "product":
// 清除商品缓存
break;
case "article":
// 清除文章缓存
break;
case "all":
// 清除所有缓存
break;
default:
return ResponseEntity.badRequest().body("无效的缓存类型");
}

return ResponseEntity.ok("缓存清除成功");
}

// 预热缓存
@PostMapping("/warmup")
public ResponseEntity<String> warmupCache() {
// 预热常用数据到缓存
// 这里可以调用相关的服务来预热数据

return ResponseEntity.ok("缓存预热完成");
}

// 缓存配置信息
@GetMapping("/config")
public ResponseEntity<CacheConfig> getCacheConfig() {
CacheConfig config = new CacheConfig();

// 返回当前缓存配置
config.setDefaultMaxAge(3600);
config.setStaticMaxAge(31536000);
config.setApiMaxAge(300);

return ResponseEntity.ok(config);
}

// 缓存统计信息类
public static class CacheStats {
private long totalRequests;
private long cacheHits;
private long cacheMisses;
private double hitRate;

// getters and setters
public long getTotalRequests() { return totalRequests; }
public void setTotalRequests(long totalRequests) { this.totalRequests = totalRequests; }

public long getCacheHits() { return cacheHits; }
public void setCacheHits(long cacheHits) { this.cacheHits = cacheHits; }

public long getCacheMisses() { return cacheMisses; }
public void setCacheMisses(long cacheMisses) { this.cacheMisses = cacheMisses; }

public double getHitRate() { return hitRate; }
public void setHitRate(double hitRate) { this.hitRate = hitRate; }
}

// 缓存配置类
public static class CacheConfig {
private int defaultMaxAge;
private int staticMaxAge;
private int apiMaxAge;

// getters and setters
public int getDefaultMaxAge() { return defaultMaxAge; }
public void setDefaultMaxAge(int defaultMaxAge) { this.defaultMaxAge = defaultMaxAge; }

public int getStaticMaxAge() { return staticMaxAge; }
public void setStaticMaxAge(int staticMaxAge) { this.staticMaxAge = staticMaxAge; }

public int getApiMaxAge() { return apiMaxAge; }
public void setApiMaxAge(int apiMaxAge) { this.apiMaxAge = apiMaxAge; }
}
}

缓存最佳实践

1. 缓存策略选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Service
public class CacheStrategyService {

// 根据资源类型选择合适的缓存策略
public CacheStrategy getCacheStrategy(String resourceType) {
switch (resourceType) {
case "static":
// 静态资源使用强缓存
return new CacheStrategy("public, max-age=31536000", null, null);

case "dynamic":
// 动态内容使用协商缓存
return new CacheStrategy("no-cache", "ETag", "Last-Modified");

case "user-specific":
// 用户特定内容使用私有缓存
return new CacheStrategy("private, max-age=300", "ETag", null);

case "api":
// API响应使用短时间缓存
return new CacheStrategy("no-cache, must-revalidate", "ETag", null);

default:
// 默认策略
return new CacheStrategy("no-cache", null, null);
}
}

// 缓存策略类
public static class CacheStrategy {
private String cacheControl;
private String etag;
private String lastModified;

public CacheStrategy(String cacheControl, String etag, String lastModified) {
this.cacheControl = cacheControl;
this.etag = etag;
this.lastModified = lastModified;
}

// getters
public String getCacheControl() { return cacheControl; }
public String getEtag() { return etag; }
public String getLastModified() { return lastModified; }
}
}

2. 缓存性能优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Component
public class CacheOptimizationService {

// 缓存性能监控
public void monitorCachePerformance() {
System.out.println("缓存性能监控:");
System.out.println("1. 监控缓存命中率");
System.out.println("2. 监控缓存响应时间");
System.out.println("3. 监控缓存大小");
System.out.println("4. 监控缓存过期情况");
}

// 缓存优化建议
public void provideOptimizationSuggestions() {
System.out.println("缓存优化建议:");
System.out.println("1. 合理设置缓存时间");
System.out.println("2. 使用ETag进行精确缓存控制");
System.out.println("3. 避免缓存雪崩");
System.out.println("4. 实现缓存预热机制");
System.out.println("5. 监控缓存性能指标");
}

// 缓存问题诊断
public void diagnoseCacheIssues() {
System.out.println("缓存问题诊断:");
System.out.println("1. 检查缓存头设置是否正确");
System.out.println("2. 检查ETag生成是否一致");
System.out.println("3. 检查Last-Modified时间是否准确");
System.out.println("4. 检查缓存策略是否合理");
System.out.println("5. 检查网络环境是否影响缓存");
}
}

3. 缓存安全考虑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service
public class CacheSecurityService {

// 缓存安全策略
public void implementCacheSecurity() {
System.out.println("缓存安全策略:");
System.out.println("1. 敏感数据不使用缓存");
System.out.println("2. 用户特定数据使用私有缓存");
System.out.println("3. 设置合适的缓存过期时间");
System.out.println("4. 避免缓存敏感信息");
System.out.println("5. 实现缓存访问控制");
}

// 缓存数据脱敏
public String sanitizeCacheData(String data) {
// 移除敏感信息
return data.replaceAll("password", "***")
.replaceAll("token", "***")
.replaceAll("secret", "***");
}

// 缓存访问控制
public boolean checkCacheAccess(String userId, String resourceId) {
// 检查用户是否有权限访问该资源
// 这里可以实现具体的权限检查逻辑
return true;
}
}

总结

通过本文的详细介绍和代码实操,我们深入了解了HTTP缓存机制:

核心要点

  1. 缓存分类:强缓存和协商缓存两种主要类型
  2. 强缓存:通过Cache-Control和Expires头实现,浏览器直接从本地获取
  3. 协商缓存:通过ETag和Last-Modified实现,需要与服务器协商
  4. Spring Boot集成:提供了完整的缓存配置和注解支持
  5. 实际应用:在电商、CMS等系统中广泛应用

最佳实践

  • 合理选择缓存策略:根据资源类型选择合适的缓存方式
  • 设置合适的缓存时间:平衡性能和数据新鲜度
  • 使用ETag进行精确控制:提供更精确的缓存验证
  • 监控缓存性能:持续优化缓存效果
  • 考虑安全因素:避免缓存敏感信息

注意事项

  • 缓存时间设置要合理,避免数据过期
  • 敏感数据不要使用缓存
  • 用户特定数据使用私有缓存
  • 定期监控缓存命中率
  • 实现缓存预热和清除机制

掌握HTTP缓存机制的原理和实践,将大大提升Web应用的性能和用户体验。