在系统开发中,字典数据(如状态类型、分类数据)具有以下特点:
为什么选择 Guava Cache?
KeyValue
@Data
@NoArgsConstructor
@AllArgsConstructor
public class KeyValue<K, V> implements Serializable {
private K key;
private V value;
}
CacheUtils
public class CacheUtils {
// 异步刷新缓存(适合全局数据)
public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
return CacheBuilder.newBuilder()
.refreshAfterWrite(duration) // 写后刷新时间
.build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool()));
}
// 同步刷新缓存(适合用户关联数据)
public static <K, V> LoadingCache<K, V> buildCache(Duration duration, CacheLoader<K, V> loader) {
return CacheBuilder.newBuilder()
.refreshAfterWrite(duration)
.build(loader);
}
}
DictFrameworkUtils
public class DictFrameworkUtils {
private static DictDataApi dictDataApi;
private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO();
/**
* 针对 {@link #getDictDataLabel(String, String)} 的缓存
*/
private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
Duration.ofMinutes(1L), // 过期时间 1 分钟
new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
@Override
public DictDataRespDTO load(KeyValue<String, String> key) {
return ObjectUtil.defaultIfNull(dictDataApi.getDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
}
});
/**
* 针对 {@link #getDictDataLabelList(String)} 的缓存
*/
private static final LoadingCache<String, List<String>> GET_DICT_DATA_LIST_CACHE = CacheUtils.buildAsyncReloadingCache(
Duration.ofMinutes(1L), // 过期时间 1 分钟
new CacheLoader<String, List<String>>() {
@Override
public List<String> load(String dictType) {
return dictDataApi.getDictDataLabelList(dictType);
}
});
/**
* 针对 {@link #parseDictDataValue(String, String)} 的缓存
*/
private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
Duration.ofMinutes(1L), // 过期时间 1 分钟
new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
@Override
public DictDataRespDTO load(KeyValue<String, String> key) {
return ObjectUtil.defaultIfNull(dictDataApi.parseDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
}
});
public static void init(DictDataApi dictDataApi) {
DictFrameworkUtils.dictDataApi = dictDataApi;
log.info("[init][初始化 DictFrameworkUtils 成功]");
}
@SneakyThrows
public static String getDictDataLabel(String dictType, String value) {
return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)).getLabel();
}
@SneakyThrows
public static List<String> getDictDataLabelList(String dictType) {
return GET_DICT_DATA_LIST_CACHE.get(dictType);
}
@SneakyThrows
public static String parseDictDataValue(String dictType, String label) {
return PARSE_DICT_DATA_CACHE.get(new KeyValue<>(dictType, label)).getValue();
}
}
public class CacheUtils {
// 异步刷新缓存(适合全局数据)
public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {
return CacheBuilder.newBuilder()
.refreshAfterWrite(duration) // 写后刷新时间
.build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool()));
}
}
关键配置说明:
private static DictDataApi dictDataApi;
private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO();
// 使用复合 Key 缓存字典项(类型+值 → 标签)
private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(
Duration.ofMinutes(1L), // 过期时间 1 分钟
new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {
@Override
public DictDataRespDTO load(KeyValue<String, String> key) {
return ObjectUtil.defaultIfNull(dictDataApi.getDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);
}
});
@SneakyThrows // 通过 Lombok 隐藏异常
public static String getDictDataLabel(String dictType, String value) {
return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)).getLabel();
}
策略 | 说明 | 本案例实现 |
---|---|---|
缓存穿透 | 非法 Key 导致频繁回源 | 返回 DICT_DATA_NULL 空对象 |
缓存雪崩 | 大量缓存同时失效 | 随机过期时间(可扩展) |
缓存击穿 | 热点 Key 失效导致并发回源 | 使用 LoadingCache 原子加载 |