一、基于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相關注解-用于精細控制緩存層