目录
前言
LoadingCache是GuavaCache构建缓存实体的方法,是一个支持多线程并发读写、高性能、通用的in-heap(堆)本地缓存。
支持key不存在时按照给定的CacheLoader 的loader方法进行loading。如果有多个线程同时get一个不存在的key,那么会有一个线程负责load,其他线程阻塞wait等待。
在工作总常常需要用到缓存,而redis往往是首选,但是短期的数据缓存一般我们还是会用到本地缓存。本文提供一个我在工作中用到的缓存工具,该工具代码为了演示做了一些调整。如果拿去使用的话,可以考虑做成注入Bean对象,看具体需求了。
本次介绍GuavaCache中LoadingCache
一、概述
CacheBuilder方法参数
三种刷新本地缓存的机制:
expireAfterAccess:当缓存项在指定的时间段内没有被读或写就会被回收。
expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收。-- 常用
refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。 -- 常用
maximumSize(): 最大缓存上限,快达到上限或达到上限,处理了时间最长没被访问过的对象或者根据配置的被释放的对象
CacheLoader
实现自动加载缓存。可以在其中自定义load方法和reload方法,根据需求加载缓存和刷新缓存。
二、环境依赖
Maven版本依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
三、示例代码
@Slf4j
@Component
public class LoadingCacheUtil implements ApplicationContextAware{
private static ApplicationContext applicationContext;
/** 缓存项最大数量 */
private static final long GUAVA_CACHE_SIZE = 10000;
/** 缓存时间:分钟 */
private static final long GUAVA_CACHE_MINUTES = 10;
/** 缓存操作对象 */
private static LoadingCache<String, Object> GLOBAL_CACHE = null;
static {
try {
GLOBAL_CACHE = loadCache(new CacheLoader <String, Object>() {
@Override
public Object load(String key) throws Exception {
// 这里是如果使用loadingCache.get(key)或者这里是如果使用loadingCache.getUnchecked(key)获取
// 残存中数据时,如果为空,则自动会放入LoadingCache内存缓存中,return值就是放入内存缓存中的值
// 同时设置的expireAfterWrite缓存失效策略,超过过期时间,也会自动将这个方法return数据加入放入内存缓存
//因在静态代码块内,所以需要applicationContext获取service层的实现类方法
Service service = applicationContext.getBean(ServiceImpl.class);
// 处理缓存键不存在缓存值时的处理逻辑
/* 在这里写具体业务实现,在service层调用get(key)方法即可 */
return null;
});
} catch (Exception e) {
log.error("初始化Guava Cache出错", e);
}
}
/**
* 全局缓存设置
* 缓存项最大数量:100000
* 缓存有效时间(分钟):10
* @param cacheLoader
* @return
* @throws Exception
*/
private static LoadingCache<String, Object> loadCache(CacheLoader<String, Object> cacheLoader)
throws Exception {
LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
//缓存池大小,在缓存项接近该大小时, Guava开始回收旧的缓存项
.maximumSize(GUAVA_CACHE_SIZE)
//设置时间对象没有被读/写访问则对象从内存中删除(在另外的线程里面不定期维护)
.refreshAfterWrite(GUAVA_CACHE_MINUTES, TimeUnit.MINUTES)
.build(cacheLoader);
return cache;
}
/**
* 设置缓存值
* 注: 若已有该key值,则会先移除(会触发removalListener移除监听器),再添加
*
* @param key
* @param value
*/
public static void put(String key, Object value) {
try {
GLOBAL_CACHE.put(key, value);
} catch (Exception e) {
log.error("设置缓存值出错", e);
}
}
/**
* 获取缓存值
* 注:如果键不存在值,将调用CacheLoader的load方法加载新值到该键中
*
* @param key
* @return
*/
public static Object get(String key) {
Object token = "";
try {
token = GLOBAL_CACHE.get(key);
} catch (Exception e) {
log.error("获取缓存值出错", e);
}
return token;
}
/**
* 刷新指定缓存值
* @param key
*/
public static void reload(String key) {
try {
GLOBAL_CACHE.refresh(key);
} catch (Exception e) {
log.error("缓存刷新错误", e);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(this.applicationContext==null){
this.applicationContext = applicationContext;
}
}
// 获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通过name获取Bean
*
* @param name
* @return Object 一个以所给名字注册的bean的实例
* @throws BeansException
*/
public static Object getBean(String name) throws BeansException {
return getApplicationContext().getBean(name);
}
}
注 :在字符串转成数组的过程中可能会出现类型转换异常,原因为数据为空。可以做以下处理:
try {
return (List<VO>) LoadingCacheUtil.get(key);
} catch (ClassCastException e){
// java.lang.ClassCastException: java.lang.String cannot be cast to java.util.List
// 转换失败,即数据为空,无法将字符串转成数组,为空直接跳过当前即可
List<VO> resultList = new ArrayList<>();
return resultList;
}
总结
短期的数据缓存使用本地缓存LoadingCache做缓存还是不错的