Springboot 从3.1开始自带的缓存功能,定义了 Cache 和 CacheManager 接口,并且提供了各种实现,如 CaffeineCache、ConcurrentMapCache、RedisCache、RedissionCache
这里通过项目案例来介绍下缓存的实现原理
缓存管理器
Spring Cache 提供的缓存管理为 CacheManager
用于管理多个缓存,提供了多种缓存管理器的实现,也支持自定义缓存管理器
public interface CacheManager {
/**
* Get the cache associated with the given name.
* <p>Note that the cache may be lazily created at runtime if the
* native provider supports it.
* @param name the cache identifier (must not be {@code null})
* @return the associated cache, or {@code null} if such a cache
* does not exist or could be not created
*/
@Nullable
Cache getCache(String name);
/**
* Get a collection of the cache names known by this manager.
* @return the names of all caches known by the cache manager
*/
Collection<String> getCacheNames();
}
自定义缓存管理器
自定义缓存管理器 PlusSpringCacheManager.java
public class PlusSpringCacheManager implements CacheManager {
private boolean dynamic = true;
private boolean allowNullValues = true;
private boolean transactionAware = true;
Map<String, CacheConfig> configMap = new ConcurrentHashMap<>();
ConcurrentMap<String, Cache> instanceMap = new ConcurrentHashMap<>();
/**
* Creates CacheManager supplied by Redisson instance
*/
public PlusSpringCacheManager() {
}
}
再通过Spring Boot的自动配置类 CacheAutoConfiguration
来进行注册,在 redis 和 tenant 中进行了自动装配
rediss 配置
/**
* redis配置
*
* @author Lion Li
*/
@Slf4j
@AutoConfiguration
@EnableCaching
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig {
/**
* 自定义缓存管理器 整合spring-cache
*/
@Bean
public CacheManager cacheManager() {
return new PlusSpringCacheManager();
}
}
TenantConfig 配置
/**
* 租户配置类
*
* @author Lion Li
*/
@EnableConfigurationProperties(TenantProperties.class)
@AutoConfiguration(after = {RedisConfig.class, MybatisPlusConfig.class})
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class TenantConfig {
/**
* 多租户缓存管理器
*/
@Primary
@Bean
public CacheManager tenantCacheManager() {
return new TenantSpringCacheManager();
}
}
缓存注解
注解说明
- cacheNames(value) : 缓存的名称
- key: 缓存的 key,Spring Expression Language (SpEL) 表达式,默认值为空,表示所有方法参数都被视为键,除非配置了自定义的 keyGenerator 的规则
- condition: SpEL 表达式,用于使方法缓存具有条件性。如果条件求值为 true,则缓存结果。默认值为"",表示始终缓存方法的结果。通过该注解可以过滤不希望进行缓存的数据
缓存注解可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。
常用的几种注解
@Cacheable
这个注解一般用在查询方法上,Spring会在其被调用后将其返回值缓存起来,下一次调用该方法前,会去检查是否缓存中已经有值,如果有就直接返回,不调用方法 ,如果没有,就调用方法,然后把结果缓存起来
/**
* 重点说明: 缓存注解严禁与其他筛选数据功能一起使用
* 例如: 数据权限注解 会造成 缓存击穿 与 数据不一致问题
* <p>
* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
*/
@Cacheable(cacheNames = CacheNames.SYS_OSS, key = "#ossId")
@Override
public SysOssVo getById(Long ossId) {
return baseMapper.selectVoById(ossId);
}
@CachePut
与@Cacheable不同的是,使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中,一般用于缓存更新的操作。
/**
* 新增参数配置
*
* @param bo 参数配置信息
* @return 结果
*/
@CachePut(cacheNames = CacheNames.SYS_CONFIG, key = "#bo.configKey")
@Override
public String insertConfig(SysConfigBo bo) {
SysConfig config = MapstructUtils.convert(bo, SysConfig.class);
int row = baseMapper.insert(config);
if (row > 0) {
return config.getConfigValue();
}
throw new ServiceException("操作失败");
}
@CacheEvict
缓存删除,当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作,标记在方法上时,执行该方法时会删除缓存
/**
* 删除部门管理信息
*
* @param deptId 部门ID
* @return 结果
*/
@CacheEvict(cacheNames = CacheNames.SYS_DEPT, key = "#deptId")
@Override
public int deleteDeptById(Long deptId) {
return baseMapper.deleteById(deptId);
}
缓存增强
Spring cache 的缓存没有过期时间的设置,项目重写了 缓存管理器,对此做了一个增强的处理。
通过 CacheNames 来执行相关设置
/**
* 缓存组名称常量
* <p>
* key 格式为 cacheNames#ttl#maxIdleTime#maxSize
* <p>
* ttl 过期时间 如果设置为0则不过期 默认为0
* maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
* maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
* <p>
* 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
*
* @author Lion Li
*/
public interface CacheNames {
/**
* 演示案例
*/
String DEMO_CACHE = "demo:cache#60s#10m#20";
}
借助缓存注解的 cacheNames 属性,解析相关参数,并将其配置到 Spring cache 的 CacheConfig配置中
@Override
public Cache getCache(String name) {
// 重写 cacheName 支持多参数
String[] array = StringUtils.delimitedListToStringArray(name, "#");
name = array[0];
Cache cache = instanceMap.get(name);
if (cache != null) {
return cache;
}
if (!dynamic) {
return cache;
}
CacheConfig config = configMap.get(name);
if (config == null) {
config = createDefaultConfig();
configMap.put(name, config);
}
if (array.length > 1) {
config.setTTL(DurationStyle.detectAndParse(array[1]).toMillis());
}
if (array.length > 2) {
config.setMaxIdleTime(DurationStyle.detectAndParse(array[2]).toMillis());
}
if (array.length > 3) {
config.setMaxSize(Integer.parseInt(array[3]));
}
if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
return createMap(name, config);
}
return createMapCache(name, config);
}