一、基于spring-cache实现多级缓存
- 实现效果,完全兼用原有SpringCache缓存使用方式和相关注解
- 通过自定义注解,驱逐的相关缓存层的数据
- 无须后续再处理多级缓存逻辑
二、具体实现代码
1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.8.8</version>
</dependency>
2、自定义缓存控制器
package top.xy23.cache;
import lombok.Getter;
import lombok.NonNull;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* 两级缓存控制器
* 一级缓存:本地缓存
* 二级缓存:Redis缓存
* author: XiaoYang
* date: 2024/5/31 下午5:16
*/
@Getter
public class TwoLevelCacheManager implements CacheManager {
private static final Logger log = LoggerFactory.getLogger(TwoLevelCacheManager.class);
private final CacheManager primaryCacheManager;
private final CacheManager secondaryCacheManager;
public TwoLevelCacheManager(CacheManager primary, CacheManager secondary) {
this.primaryCacheManager = primary;
this.secondaryCacheManager = secondary;
}
@Override
public Cache getCache(@NonNull String name) {
Cache primary = primaryCacheManager.getCache(name);
Cache secondary = secondaryCacheManager.getCache(name);
if (primary == null || secondary == null) {
log.warn("未找到名为 {} 的缓存", name);
return null;
}
return new TwoLevelCache(primary, secondary);
}
@Override
public @NonNull Collection<String> getCacheNames() {
List<String> cacheNames = new ArrayList<>();
cacheNames.addAll(primaryCacheManager.getCacheNames());
cacheNames.addAll(secondaryCacheManager.getCacheNames());
return cacheNames;
}
}
3、自定义缓存实现
package top.xy23.cache;
import lombok.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import java.util.concurrent.Callable;
/**
* 缓存层
* author: XiaoYang
* date: 2024/6/4 下午2:01
*/
public class TwoLevelCache implements Cache {
private static final Logger log = LoggerFactory.getLogger(TwoLevelCache.class);
private final Cache primary;
private final Cache secondary;
public TwoLevelCache(Cache primary, Cache secondary) {
this.primary = primary;
this.secondary = secondary;
}
@Override
public @NonNull String getName() {
return primary.getName();
}
@Override
public @NonNull Object getNativeCache() {
return primary.getNativeCache();
}
@Override
public ValueWrapper get(@NonNull Object key) {
ValueWrapper value = primary.get(key);
if (value == null) {
value = secondary.get(key);
if (value != null) {
primary.put(key, value.get());
}
}
return value;
}
@Override
public <T> T get(@NonNull Object key, Class<T> type) {
T value = primary.get(key, type);
if (value == null) {
value = secondary.get(key, type);
if (value != null) {
primary.put(key, value);
}
}
return value;
}
@Override
public @NonNull <T> T get(@NonNull Object key,@NonNull Callable<T> valueLoader) {
T value = primary.get(key, valueLoader);
if (value == null) {
try {
value = valueLoader.call();
secondary.put(key, value);
primary.put(key, value);
} catch (Exception e) {
log.error("加载缓存值失败,键为 {}", key, e);
throw new ValueRetrievalException(key, valueLoader, e);
}
}
return value;
}
@Override
public void put(@NonNull Object key, Object value) {
primary.put(key, value);
secondary.put(key, value);
}
@Override
public void evict(@NonNull Object key) {
primary.evict(key);
secondary.evict(key);
}
@Override
public void clear() {
primary.clear();
secondary.clear();
}
}
4、将自定义的缓存管理器注入Spring容器
package top.xy23.config;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import top.xy23.cache.TwoLevelCacheManager;
import java.time.Duration;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public Caffeine<Object, Object> caffeineConfig() {
return Caffeine.newBuilder()
.expireAfterWrite(Duration.ofSeconds(30))
.maximumSize(100);
}
@Bean
@Primary
public TwoLevelCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, Caffeine<Object, Object> caffeine) {
// 本地缓存使用 Caffeine
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(caffeine);
// redis缓存
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)); // 设置默认的expire时间为1小时
RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
// 自定义缓存管理器
return new TwoLevelCacheManager(caffeineCacheManager, redisCacheManager);
}
}
5、可选-自定义驱逐缓存注解-用于精细控制缓存层
5.1、自定义注解-TwoLevelCacheEvict
package top.xy23.cache.aop;
import org.springframework.core.annotation.AliasFor;
import top.xy23.cache.CacheType;
import java.lang.annotation.*;
/**
* 自定义缓存驱逐器用于设置要驱逐的缓存
* author: XiaoYang
* date: 2024/6/4 上午8:42
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TwoLevelCacheEvict {
/**
* 要驱逐的缓存类型,默认为全部
*/
CacheType cacheType() default CacheType.ALL;
/**
* Alias for {@link #cacheNames}.
*/
@AliasFor("cacheNames")
String[] value() default {};
/**
* 用于缓存逐出操作的缓存的名称。
* 名称可用于确定目标高速缓存(或高速缓存),与特定 Bean 定义的限定符值或 Bean 名称匹配
*/
@AliasFor("value")
String[] cacheNames() default {};
/**
Spring 表达式语言 (SpEL) 表达式,用于动态计算密钥。
默认值为 "",表示除非设置了自定义 keyGenerator 量,否则所有方法参数都被视为键。
SpEL 表达式根据提供以下元数据的专用上下文进行计算:
#result作为对方法调用结果的引用,只有在 is false时才能使用beforeInvocation()。对于支持的包装器,例如 Optional,#result指的是实际对象,而不是包装器
#root. method、 #root. target和 #root. caches 分别用于对 method、目标对象和受影响缓存的引用。
方法名称 (#root. methodName) 和目标类 (#root. targetClass) 的快捷方式也可用。
方法参数可以通过索引访问。例如,可以通过 或 #p1 #a1访问#root. args[1]第二个参数。如果该信息可用,也可以按名称访问参数。
*/
String key() default "";
/**
* 是否删除缓存中的所有条目。
* <p>默认情况下,仅删除关联键下的值。
* <p>请注意,将此参数设置为 {@code true} 并指定
* {@link #key} 是不允许的。
*/
boolean allEntries() default false;
}
5.2、自定义切面-处理TwoLevelCacheEvict注解
package top.xy23.cache.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import top.xy23.cache.CacheType;
import top.xy23.cache.TwoLevelCacheManager;
/**
* 自定义缓存清除切面
* author: XiaoYang
* date: 2024/6/4 上午8:58
*/
@Aspect
@Component
@Slf4j
public class TwoLeveCacheEvictAspect {
@Autowired
private TwoLevelCacheManager twoLevelCacheManager;
@Around("@annotation(twoLevelCacheEvict)")
public Object evictCache(ProceedingJoinPoint pjp, TwoLevelCacheEvict twoLevelCacheEvict) throws Throwable {
evictCaches(twoLevelCacheEvict);
return pjp.proceed();
}
private void evictCaches(TwoLevelCacheEvict twoLevelCacheEvict) {
CacheManager cacheManager;
CacheType cacheType = twoLevelCacheEvict.cacheType();
switch (cacheType) {
case PRIMARY:
log.info("Evicting primary caches");
cacheManager = twoLevelCacheManager.getPrimaryCacheManager();
break;
case SECONDARY:
log.info("Evicting secondary caches");
cacheManager = twoLevelCacheManager.getSecondaryCacheManager();
break;
case ALL:
log.info("Evicting all caches");
cacheManager = twoLevelCacheManager;
break;
default:
throw new IllegalArgumentException();
}
for (String cacheName : twoLevelCacheEvict.cacheNames()) {
Cache cache = cacheManager.getCache(cacheName);
if (cache == null) {
continue;
}
try {
if (twoLevelCacheEvict.allEntries()) {
cache.clear();
} else {
cache.evict(twoLevelCacheEvict.key());
}
} catch (Exception ignored) {
}
}
}
}
5.3、缓存层级枚举类
package top.xy23.cache;
/**
* author: XiaoYang
* date: 2024/6/4 上午8:54
*/
public enum CacheType {
/**
* 全部缓存
*/
ALL,
/**
* 主缓存/一级缓存
*/
PRIMARY,
/**
* 二级缓存
*/
SECONDARY;
}
6、未完-继续完善自定义put相关注解-用于精细控制缓存层