第11集Java实战代码实操-HTTP缓存机制详解
|字数总计:5.3k|阅读时长:26分钟|阅读量:
引言
HTTP缓存是Web开发中提升性能的重要手段,通过合理使用缓存机制,可以显著减少网络请求,降低服务器负载,提升用户体验。本文将深入探讨HTTP缓存的分类、实现原理,并通过Java Spring Boot实战代码演示如何在项目中应用HTTP缓存机制。
HTTP缓存概述
什么是HTTP缓存
HTTP缓存是指浏览器或代理服务器将Web资源(HTML、CSS、JS、图片等)存储在本地,当再次请求相同资源时,直接从本地缓存中获取,而不需要重新从服务器下载。
缓存的好处
- 提升性能:减少网络请求,加快页面加载速度
- 降低服务器负载:减少服务器处理请求的压力
- 节省带宽:减少网络传输的数据量
- 改善用户体验:页面响应更快,用户体验更佳
HTTP缓存分类
HTTP缓存主要分为两类:
- 强缓存:浏览器直接从本地缓存中获取资源,不发送请求到服务器
- 协商缓存:浏览器发送请求到服务器,服务器判断资源是否更新,决定返回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 { @GetMapping("/strong-cache") public ResponseEntity<String> strongCache() { String content = "这是强缓存内容,缓存时间180秒"; HttpHeaders headers = new HttpHeaders(); headers.setCacheControl("max-age=180"); return ResponseEntity.ok() .headers(headers) .body(content); } @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); } @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); } @GetMapping("/no-cache") public ResponseEntity<String> noCache() { String content = "这是no-cache内容,需要验证缓存"; HttpHeaders headers = new HttpHeaders(); headers.setCacheControl("no-cache"); return ResponseEntity.ok() .headers(headers) .body(content); } @GetMapping("/no-store") public ResponseEntity<String> noStore() { String content = "这是no-store内容,不允许缓存"; HttpHeaders headers = new HttpHeaders(); 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(); Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.HOUR, 1); 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); } @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))); 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) { 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; 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); 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); } @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); 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资源内容"; 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); } @GetMapping("/dynamic-resource") public ResponseEntity<String> getDynamicResource(HttpServletRequest request) { String content = "动态ETag资源内容,时间戳:" + System.currentTimeMillis(); 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); } @GetMapping("/weak-etag") public ResponseEntity<String> getWeakETagResource(HttpServletRequest request) { String content = "这是弱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); } 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) { 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 { 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; } public boolean checkETagCache(HttpServletRequest request, String etag) { String ifNoneMatch = request.getHeader("If-None-Match"); return ifNoneMatch != null && ifNoneMatch.equals(etag); } 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))); } public void setETagHeader(HttpServletResponse response, String etag) { response.setHeader("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() + "\""; } } 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) { 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"); } 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"); } } private void postProcessCacheHeaders(HttpServletRequest request, HttpServletResponse response) { String requestURI = request.getRequestURI(); if (requestURI.startsWith("/api/")) { 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(); } String etag = cacheService.generateETag(product.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=3600"); 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); 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"); 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"); 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(); } 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"); 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); 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"); 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"); 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; 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; 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": 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; } 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缓存机制:
核心要点
- 缓存分类:强缓存和协商缓存两种主要类型
- 强缓存:通过Cache-Control和Expires头实现,浏览器直接从本地获取
- 协商缓存:通过ETag和Last-Modified实现,需要与服务器协商
- Spring Boot集成:提供了完整的缓存配置和注解支持
- 实际应用:在电商、CMS等系统中广泛应用
最佳实践
- 合理选择缓存策略:根据资源类型选择合适的缓存方式
- 设置合适的缓存时间:平衡性能和数据新鲜度
- 使用ETag进行精确控制:提供更精确的缓存验证
- 监控缓存性能:持续优化缓存效果
- 考虑安全因素:避免缓存敏感信息
注意事项
- 缓存时间设置要合理,避免数据过期
- 敏感数据不要使用缓存
- 用户特定数据使用私有缓存
- 定期监控缓存命中率
- 实现缓存预热和清除机制
掌握HTTP缓存机制的原理和实践,将大大提升Web应用的性能和用户体验。