在SpringBoot中缓存HTTP请求响应体的方法

发布时间:2021-06-28 16:37:39 作者:chen
来源:亿速云 阅读:577
# 在SpringBoot中缓存HTTP请求响应体的方法

## 引言

在现代Web应用开发中,性能优化是一个永恒的话题。HTTP请求响应体的缓存作为提升系统性能的重要手段,能够显著减少重复计算、降低数据库压力并加快客户端响应速度。SpringBoot作为Java生态中最流行的微服务框架,提供了丰富的缓存支持。

本文将深入探讨在SpringBoot应用中实现HTTP请求响应体缓存的完整方案,涵盖从基础概念到高级实现的各个层面,帮助开发者构建高性能的Web服务。

## 一、HTTP缓存基础概念

### 1.1 什么是HTTP缓存

HTTP缓存是指将请求的响应内容存储在中间介质(内存、磁盘等)中,当相同请求再次发生时直接返回已存储的内容,而非重新执行完整处理流程的技术。

```java
// 典型缓存流程示例
if (缓存中存在请求结果) {
    return 缓存结果;
} else {
    执行业务逻辑;
    存储结果到缓存;
    return 结果;
}

1.2 缓存的关键优势

1.3 缓存类型对比

缓存类型 存储位置 速度 容量限制 典型应用场景
浏览器缓存 客户端 最快 静态资源缓存
CDN缓存 边缘节点 全局内容分发
反向代理缓存 服务端前置 较快 较大 全页缓存
应用层缓存 应用内存 极快 较小 动态数据缓存
分布式缓存 独立服务 共享数据缓存

二、SpringBoot缓存机制详解

2.1 Spring缓存抽象

Spring框架提供了统一的缓存抽象层,通过org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口支持多种缓存实现。

核心注解: - @Cacheable:标记可缓存方法 - @CacheEvict:清除缓存项 - @CachePut:更新缓存而不干扰方法执行 - @Caching:组合多个缓存操作 - @CacheConfig:类级别共享缓存配置

2.2 内置缓存实现

SpringBoot支持多种缓存实现,通过简单配置即可切换:

  1. Caffeine(推荐):

    @Bean
    public CaffeineCacheManager cacheManager() {
       CaffeineCacheManager cacheManager = new CaffeineCacheManager();
       cacheManager.setCaffeine(Caffeine.newBuilder()
           .expireAfterWrite(10, TimeUnit.MINUTES)
           .maximumSize(1000));
       return cacheManager;
    }
    
  2. EhCache

    <!-- ehcache.xml -->
    <cache name="responseCache"
          maxEntriesLocalHeap="1000"
          timeToLiveSeconds="600"/>
    
  3. Redis(分布式场景):

    spring:
     cache:
       type: redis
       redis:
         time-to-live: 600000
         key-prefix: "CACHE_"
         use-key-prefix: true
    

2.3 缓存配置最佳实践

推荐配置组合:

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager() {
            @Override
            protected Cache createConcurrentMapCache(String name) {
                return new ConcurrentMapCache(name,
                    Caffeine.newBuilder()
                        .expireAfterWrite(30, TimeUnit.MINUTES)
                        .maximumSize(1000)
                        .build().asMap(),
                    false);
            }
        };
        return cacheManager;
    }
    
    @Bean
    public KeyGenerator customKeyGenerator() {
        return (target, method, params) -> {
            StringBuilder key = new StringBuilder();
            key.append(target.getClass().getSimpleName());
            key.append(".");
            key.append(method.getName());
            for (Object param : params) {
                if (param != null) {
                    key.append("_");
                    if (param instanceof HttpServletRequest) {
                        key.append("req_")
                           .append(((HttpServletRequest) param).getRequestURI());
                    } else {
                        key.append(param.toString());
                    }
                }
            }
            return key.toString();
        };
    }
}

三、HTTP响应体缓存实现方案

3.1 控制器方法级缓存

最直接的实现方式是在Controller方法上添加缓存注解:

@RestController
@RequestMapping("/api/products")
public class ProductController {

    @GetMapping("/{id}")
    @Cacheable(value = "productResponses", key = "#id")
    public ResponseEntity<Product> getProduct(@PathVariable Long id) {
        Product product = productService.findById(id);
        return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.MINUTES))
            .eTag(product.getVersion().toString())
            .body(product);
    }
}

3.2 使用Spring的ResponseBodyAdvice

对于统一格式的响应,可以实现ResponseBodyAdvice进行全局处理:

@ControllerAdvice
public class CachingResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    @Autowired
    private CacheManager cacheManager;

    @Override
    public boolean supports(MethodParameter returnType, 
                          Class<? extends HttpMessageConverter<?>> converterType) {
        return returnType.hasMethodAnnotation(Cacheable.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                MediaType selectedContentType,
                                Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                ServerHttpRequest request, ServerHttpResponse response) {
        
        Cacheable cacheable = returnType.getMethodAnnotation(Cacheable.class);
        if (cacheable != null) {
            String cacheName = cacheable.value()[0];
            Cache cache = cacheManager.getCache(cacheName);
            if (cache != null) {
                String key = generateKey(request, returnType);
                cache.put(key, body);
                
                // 设置HTTP缓存头
                if (response instanceof ServletServerHttpResponse) {
                    HttpServletResponse servletResponse = 
                        ((ServletServerHttpResponse) response).getServletResponse();
                    servletResponse.setHeader("Cache-Control", "max-age=1800");
                }
            }
        }
        return body;
    }
    
    private String generateKey(ServerHttpRequest request, MethodParameter returnType) {
        // 实现自定义key生成逻辑
    }
}

3.3 使用Filter实现全请求缓存

对于更底层的控制,可以创建缓存Filter:

public class CachingFilter extends OncePerRequestFilter {

    @Autowired
    private CacheManager cacheManager;

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                  HttpServletResponse response,
                                  FilterChain filterChain) throws IOException, ServletException {
        
        String cacheKey = generateCacheKey(request);
        Cache cache = cacheManager.getCache("httpResponses");
        
        if (cache != null) {
            CachedResponse cachedResponse = cache.get(cacheKey, CachedResponse.class);
            if (cachedResponse != null) {
                writeCachedResponse(cachedResponse, response);
                return;
            }
        }
        
        ContentCachingResponseWrapper responseWrapper = 
            new ContentCachingResponseWrapper(response);
        filterChain.doFilter(request, responseWrapper);
        
        if (cache != null && response.getStatus() == 200) {
            CachedResponse responseToCache = new CachedResponse(
                responseWrapper.getContentAsByteArray(),
                response.getContentType(),
                response.getHeader("ETag"));
            cache.put(cacheKey, responseToCache);
        }
        responseWrapper.copyBodyToResponse();
    }
    
    // 辅助方法实现...
}

3.4 分布式缓存集成

在微服务架构中,通常需要Redis等分布式缓存:

@Configuration
public class RedisCacheConfig {

    @Bean
    public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
        return builder -> builder
            .withCacheConfiguration("productResponses",
                RedisCacheConfiguration.defaultCacheConfig()
                    .entryTtl(Duration.ofMinutes(30))
                    .disableCachingNullValues()
                    .serializeValuesWith(SerializationPair.fromSerializer(
                        new Jackson2JsonRedisSerializer<>(Object.class))));
    }
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

四、高级缓存策略与优化

4.1 缓存失效策略

  1. 基于TTL的自动失效

    @Cacheable(value = "responses", key = "#root.methodName", 
             unless = "#result == null")
    @CacheConfig(cacheNames = "responses")
    
  2. 手动清除策略: “`java @CacheEvict(value = “responses”, allEntries = true) public void clearAllResponses() {}

@CacheEvict(value = “responses”, key = “#id”) public void evictById(Long id) {}


3. **条件缓存**:
   ```java
   @Cacheable(condition = "#request.getHeader('no-cache') == null")

4.2 缓存键设计策略

良好的缓存键设计应考虑: - 包含所有影响响应的参数 - 排除无关参数(如时间戳) - 保持合理长度

示例实现:

public class CacheKeyGenerator implements KeyGenerator {

    @Override
    public Object generate(Object target, Method method, Object... params) {
        StringBuilder key = new StringBuilder();
        key.append(target.getClass().getSimpleName());
        key.append("_");
        key.append(method.getName());
        
        if (params.length > 0) {
            key.append("_");
            for (Object param : params) {
                if (param instanceof HttpServletRequest) {
                    HttpServletRequest request = (HttpServletRequest) param;
                    key.append(request.getRequestURI());
                    Enumeration<String> paramNames = request.getParameterNames();
                    while (paramNames.hasMoreElements()) {
                        String name = paramNames.nextElement();
                        if (!name.equals("_")) { // 排除随机参数
                            key.append("_").append(name)
                               .append("=").append(request.getParameter(name));
                        }
                    }
                } else if (param != null) {
                    key.append("_").append(param.toString());
                }
            }
        }
        
        return DigestUtils.md5DigestAsHex(key.toString().getBytes());
    }
}

4.3 缓存雪崩与穿透防护

  1. 雪崩防护: “`java // 随机TTL避免同时失效 @Cacheable(value = “products”, key = “#id”, cacheManager = “randomTtlCacheManager”)

// 配置类 @Bean public CacheManager randomTtlCacheManager() { return new CaffeineCacheManager() { @Override protected Cache createCaffeineCache(String name) { return new CaffeineCache(name, Caffeine.newBuilder() .expireAfterWrite(30 + new Random().nextInt(15), TimeUnit.MINUTES) .build()); } }; }


2. **穿透防护**:
   ```java
   @Cacheable(value = "products", 
             key = "#id",
             unless = "#result == null")
   public Product getProduct(Long id) {
       Product product = productRepository.findById(id);
       if (product == null) {
           // 缓存空值防止穿透
           cacheNullValue(id);
           return null;
       }
       return product;
   }

五、性能监控与调优

5.1 缓存命中率监控

集成Micrometer进行指标收集:

@Configuration
public class CacheMetricsConfig {

    @Bean
    public CacheMetricsRegistrar cacheMetricsRegistrar(
            CacheManager cacheManager, MeterRegistry meterRegistry) {
        return new CacheMetricsRegistrar(cacheManager, meterRegistry)
            .bindTo(meterRegistry);
    }
}

5.2 日志记录与调试

配置缓存操作日志:

@Slf4j
@Aspect
@Component
public class CacheLoggerAspect {

    @Pointcut("@annotation(org.springframework.cache.annotation.Cacheable)")
    public void cacheableMethods() {}

    @Pointcut("@annotation(org.springframework.cache.annotation.CacheEvict)")
    public void cacheEvictMethods() {}

    @Around("cacheableMethods()")
    public Object logCacheable(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        
        log.debug("Checking cache for {} with args {}", methodName, args);
        Object result = joinPoint.proceed();
        
        if (result != null) {
            log.debug("Cache hit for {}", methodName);
        } else {
            log.debug("Cache miss for {}", methodName);
        }
        return result;
    }
}

5.3 JMX管理

启用缓存JMX管理:

spring.cache.jmx.enabled=true

六、实战案例:电商API缓存实现

6.1 商品详情缓存

@RestController
@RequestMapping("/api/v2/products")
@CacheConfig(cacheNames = "productDetail")
public class ProductControllerV2 {

    @GetMapping("/{id}")
    @Cacheable(key = "T(com.example.util.CacheKeyGenerator).generateDetailKey(#id, #request)")
    public ResponseEntity<ProductDetailDTO> getProductDetail(
            @PathVariable Long id,
            @RequestHeader(value = "Accept-Language", defaultValue = "zh-CN") String language,
            WebRequest request) {
        
        if (request.checkNotModified(getLastModified(id))) {
            return null;
        }
        
        ProductDetailDTO detail = productService.getDetail(id, language);
        return ResponseEntity.ok()
            .cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS))
            .eTag(detail.getVersion())
            .lastModified(detail.getUpdateTime().toEpochMilli())
            .body(detail);
    }
}

6.2 商品列表缓存

@GetMapping
@Cacheable(key = "{#page, #size, #sort, #request.queryString}")
public Page<ProductListItem> listProducts(
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "20") int size,
        @RequestParam(defaultValue = "id,desc") String sort,
        HttpServletRequest request) {
    
    return productService.findProducts(PageRequest.of(page, size, Sort.by(sort)));
}

6.3 缓存更新策略

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleProductUpdate(ProductUpdatedEvent event) {
    cacheEvictor.evictProductCache(event.getProductId());
}

@Service
@RequiredArgsConstructor
public class CacheEvictor {
    
    private final CacheManager cacheManager;
    
    public void evictProductCache(Long productId) {
        // 清除详情缓存
        Cache detailCache = cacheManager.getCache("productDetail");
        if (detailCache != null) {
            detailCache.evict(CacheKeyGenerator.generateDetailKey(productId));
        }
        
        // 清除相关列表缓存
        Cache listCache = cacheManager.getCache("productList");
        if (listCache != null) {
            listCache.clear();
        }
    }
}

七、常见问题与解决方案

7.1 缓存一致性挑战

问题场景: - 数据库更新后缓存未及时失效 - 分布式环境下各节点缓存不一致

解决方案: 1. 使用Spring的@TransactionalEventListener确保事务提交后清除缓存 2. 引入消息队列广播缓存失效事件 3. 实现双删策略:

   @CacheEvict(value = "products", key = "#product.id")
   @Transactional
   public Product updateProduct(Product product) {
       product = productRepository.save(product);
       eventPublisher.publishEvent(new ProductUpdatedEvent(product.getId()));
       return product;
   }
   
   // 事件处理器延迟二次删除
   @EventListener
   @Async
   public void handleProductUpdated(ProductUpdatedEvent event) {
       try {
           Thread.sleep(1000);
           cacheManager.getCache("products").evict(event.getProductId());
       } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
       }
   }

7.2 大Value处理

优化方案: 1. 压缩缓存数据:

   @Bean
   public RedisTemplate<String, Object> redisTemplate(
           RedisConnectionFactory connectionFactory) {
       // 配置压缩序列化器
       template.setValueSerializer(new GzipRedisSerializer(
           new GenericJackson2JsonRedisSerializer()));
   }
  1. 分片存储大对象
  2. 使用引用存储: “`java @Cacheable(value = “productRefs”) public String getProductReference(Long id) { return “product::” + id; }

@Cacheable(value = “productData”, key = “#ref”) public Product getProductByRef(String ref) { Long id = extractIdFromRef(ref); return productRepository.findById(id); }


### 7.3 敏感数据缓存

**安全措施**:
1. 排除敏感字段:
   ```java
   @JsonIgnore
   private String password;
  1. 单独缓存脱敏数据
  2. 加密存储:
    
    @Bean
    public CacheManager secureCacheManager() {
       return new CaffeineCacheManager() {
           @Override
           protected Cache createCaffeineCache(String name) {
               return new SecureCacheWrapper(
                   super.createCaffeineCache(name),
                   encryptionService);
           }
       };
    }
    

八、未来发展与替代方案

8.1 HTTP/2 Server Push

对于相关资源可考虑使用HTTP/2推送:

推荐阅读:
  1. HTTP的请求方法
  2. HTTP协议(5)HTTP请求和响应

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

springboot

上一篇:Android中怎么实现图片压缩功能

下一篇:Android 中怎么通过ActionBarActivity设置全屏无标题

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》