目录
什么是 Spring Cache?
Spring Cache是Spring框架提供的一层缓存抽象,旨在简化应用程序中的缓存管理。通过使用Spring Cache,开发者能够在方法级别方便地定义缓存策略,提高应用性能、响应速度,并减轻底层数据源的负载。该框架提供一系列注解,如@Cacheable
、@CacheEvict
、@CachePut
,以及对多种底层缓存实现的支持,如EhCache、Redis等。它为应用程序提供了一种统一、方便、灵活的缓存管理方式,允许开发者通过简单的配置实现复杂的缓存逻辑,同时与Spring框架紧密集成。这种抽象层的存在使得在更改底层缓存框架时更为轻松,同时提供了一致的配置接口和更强大的高级特性。
Spring Cache 常用 API
@Cacheable
@Cacheable
: 用于标记一个方法的结果应该被缓存。当在该方法被调用时,Spring首先检查缓存中是否已经有了预期的结果,如果有,直接返回缓存中的结果而不执行实际的方法体。
javaCopy code@Cacheable(cacheNames = "exampleCache", key = "'exampleKey'")
public String getCachedData() {
// 执行实际的方法体,结果会被缓存
}
@CacheEvict
@CacheEvict
: 用于标记一个方法在执行后清空缓存。可以指定清空的缓存名称和清空的条件。
javaCopy code@CacheEvict(cacheNames = "exampleCache", key = "'exampleKey'")
public void clearCache() {
// 执行实际的方法体,之后清空缓存
}
@CachePut
@CachePut
: 用于标记一个方法的结果应该被放入缓存,常用于在方法执行后更新缓存。
javaCopy code@CachePut(cacheNames = "exampleCache", key = "'exampleKey'")
public String updateCache() {
// 执行实际的方法体,结果会被放入缓存
}
@Caching
@Caching
: 允许同时应用多个缓存操作注解。
javaCopy code@Caching(
evict = {
@CacheEvict(cacheNames = "cache1", key = "'key1'"),
@CacheEvict(cacheNames = "cache2", key = "'key2'")
}
)
public void clearMultipleCaches() {
// 执行实际的方法体,清空多个缓存
}
Spring Boot 整合 Spring Cache (Redis 缓存)
完整项目源码:youlai-boot
项目依赖 pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
项目配置 application.yml
spring:
data:
redis:
database: 6
host: localhost
port: 6379
password: 123456
timeout: 10s
lettuce:
pool:
# 连接池最大连接数 默认8 ,负数表示没有限制
max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1
max-wait: -1
# 连接池中的最大空闲连接 默认8
max-idle: 8
# 连接池中的最小空闲连接 默认0
min-idle: 0
cache:
# 缓存类型 redis、none(不使用缓存)
type: redis
# 缓存时间(单位:ms)
redis:
time-to-live: 3600000
# 缓存null值,防止缓存穿透
cache-null-values: true
自动装配配置类 RedisCacheConfig
package com.youlai.system.config;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* Redis 缓存配置
*
* @author haoxr
* @since 2023/12/4
*/
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
public class RedisCacheConfig {
/**
* 自定义 RedisCacheManager
* <p>
* 修改 Redis 序列化方式,默认 JdkSerializationRedisSerializer
*
* @param redisConnectionFactory {@link RedisConnectionFactory}
* @param cacheProperties {@link CacheProperties}
* @return {@link RedisCacheManager}
*/
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory, CacheProperties cacheProperties){
return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration(cacheProperties))
.build();
}
/**
* 自定义 RedisCacheConfiguration
*
* @param cacheProperties {@link CacheProperties}
* @return {@link RedisCacheConfiguration}
*/
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
config = config.computePrefixWith(name -> name + ":");// 覆盖默认key双冒号 CacheKeyPrefix#prefixed
return config;
}
}
Spring Boot 路由缓存实战
完整项目源码:youlai-boot
获取路由数据缓存
获取路由列表,通过 @Cacheable
注解,将方法返回的路由列表数据缓存至 Redis。当再次请求时,不再进入方法体,而是直接从 Redis 中读取缓存数据并返回,以提高性能。
/**
* 获取路由列表
*/
@Override
@Cacheable(cacheNames = "menu", key = "'routes'") // cacheNames 为必填项,key 需要使用引号,否则会被识别为变量。
public List<RouteVO> listRoutes() {
List<RouteBO> menuList = this.baseMapper.listRoutes();
return buildRoutes(SystemConstants.ROOT_NODE_ID, menuList);
}
更新路由缓存失效
若需要在路由信息更新时使缓存失效,可以使用 @CacheEvict
注解,它用于在方法执行之后(默认)从缓存中移除条目。
/**
* 新增/修改菜单
*/
@Override
@CacheEvict(cacheNames = "menu", key = "'routes'",beforeInvocation = true)
public boolean saveMenu(MenuForm menuForm) {
String path = menuForm.getPath();
MenuTypeEnum menuType = menuForm.getType();
// 如果是目录
if (menuType == MenuTypeEnum.CATALOG) {
if (menuForm.getParentId() == 0 && !path.startsWith("/")) {
menuForm.setPath("/" + path); // 一级目录需以 / 开头
}
menuForm.setComponent("Layout");
}
// 如果是外链
else if (menuType == MenuTypeEnum.EXTLINK) {
menuForm.setComponent(null);
}
SysMenu entity = menuConverter.form2Entity(menuForm);
String treePath = generateMenuTreePath(menuForm.getParentId());
entity.setTreePath(treePath);
return this.saveOrUpdate(entity);
}
更新菜单后,Redis 中缓存的路由数据将被清除。再次获取路由时,才会将新的路由数据缓存到 Redis 中。
Spring Cache 问题整理
@Cacheable 缓存的 key 双冒号
默认情况下 @Cacheable 使用双冒号 ::
拼接 cacheNames
和 key
@Cacheable(cacheNames = "menu", key = "'routes'")
参考源码 RedisCacheConfiguration#prefixCacheNameWith
如果需要将双冒号改成单个冒号,需要重写 RedisCacheConfiguration#computePrefixWith
方法
config = config.computePrefixWith(name -> name + ":");//覆盖默认key双冒号 CacheKeyPrefix#prefixed
结语
Spring Cache 为 Spring 应用程序提供了简便的缓存管理抽象,通过注解如 @Cacheable
、@CacheEvict
、@CachePut
,开发者能方便定义缓存策略。整合Spring Boot 与 Redis
缓存实例展示了如何配置、使用Spring Cache,提升应用性能。通过实战演示菜单路由缓存,突显 @Cacheable
和 @CacheEvict
的实际应用,以及解决 @Cacheable
默认key双冒号的问题。
开源项目
- SpringCloud + Vue3 微服务商城
- SpringBoot 3+ Vue3 单体权限管理系统