最近在工作中使用了CaffeineCache本地缓存框架来对一些数据做缓存,提高整体的效率。
CaffeineCache在创建缓存对象时能够自行选择需要的缓存过期策略,缓存过期策略主要包括:
expireAfterWrite 第一次写后一段时间过期:例如,设置5分钟后过期,第一次写入时间为15:00,在此期间无论对缓存多少次读操作,缓存都会在15:05过期。
expireAfterAccess 读后过一段时间过期,如果这段时间内再次读,那么重新开始计算时间:例如,设置读时间5分钟后过期,第一次写入时间为15:00,若之后未对该缓存进行任何读操作,那它就会在15:05过期;假设在15:02对该缓存进行了一次读操作,并且后续没有任何读操作,那么它将在15:07过期。
前面两种过期方式是CaffeineCache对所有缓存KV的过期管理,如果需要对缓存中各个KV进行更细粒度的管理,CaffeineCache也提供了相应的缓存策略接口:
在创建CaffeineCache时,通过expireAfter方法来实现用户自定义的缓存策略。下面给出一个参考代码:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class CaffeineEntryCacheUtil {
private Cache<String, CaffeineEntry> cache;
/**
* 初始化,创建可自定义控制过期时间的cache
*
* @param maximumSize
*/
private CaffeineEntryCacheUtil(Long maximumSize) {
Cache<String, CaffeineEntry> build = Caffeine.newBuilder()
.maximumSize(maximumSize)
.expireAfter(new Expiry<String, CaffeineEntry>() {
@Override
public long expireAfterCreate(@NonNull String s, @NonNull CaffeineEntry caffeineEntry, long l) {
return caffeineEntry.getExpireTime();
}
@Override
public long expireAfterUpdate(@NonNull String s, @NonNull CaffeineEntry caffeineEntry, long l, @NonNegative long l1) {
return caffeineEntry.getExpireTime();
}
@Override
public long expireAfterRead(@NonNull String s, @NonNull CaffeineEntry caffeineEntry, long l, @NonNegative long l1) {
if (caffeineEntry.isAccessFresh()) {
return caffeineEntry.getExpireTime();
}
return l1;
}
}).build();
this.cache = build;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
static class CaffeineEntry {
// key
private String key;
// 所存储的数据
private Object value;
// 该KV的过期时间
private long expireTime;
// 是否在读取时进行刷新
private boolean accessFresh;
}
}
在上面的代码当中,我在CaffeineEntryUtil这个类中定义了一个静态内部类CaffeineEntry,用于对我需要缓存的数据进行一层额外的包装,通过这层包装,我能够指定KV的过期时间,以及在读取时是否对过期时间进行刷新,这样就能够对缓存中的各个KV的过期时间进行更细粒度的管理。
expireAfter中三个方法的返回值会成为KV的过期时间,或者说是该KV距离过期的剩余时间。
expireAfterCreate:在KV被创建时触发,这里返回的是caffeineEntry.getExpireTime()。
expireAfterUpdate:在KV被更新时触发,即当前缓存中若有KEY为 "SB" 的数据,那么在缓存外部调用put("SB", newValue)时,这个方法就会被触发。如果将方法入参当中的l1进行返回,就是将该KV当前的剩余时间返回,即原先KV如果是5秒后过期,那么方法结束后它仍是5秒后过期;如果返回caffeineEntry.getExpireTime(),则是将该KV的剩余时间修改为expireTime字段所存储的时间。
expireAfterRead:在KV被读取时触发,假若当前缓存中有KEY为 "SB" 的数据,那么在缓存外部调用getIfPresent("SB")对该KV进行读取时,这个方法就会被触发。如果返回l1则是不对剩余时间进行修改。
下面贴出整个工具的完整代码:
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Expiry;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.*;
import java.util.concurrent.TimeUnit;
public class CaffeineEntryCacheUtil {
private Cache<String, CaffeineEntry> cache;
/**
* 初始化,创建可自定义控制过期时间的cache
* l1是当前键剩余的时间
*
* @param maximumSize
*/
private CaffeineEntryCacheUtil(Long maximumSize) {
Cache<String, CaffeineEntry> build = Caffeine.newBuilder()
.maximumSize(maximumSize)
.expireAfter(new Expiry<String, CaffeineEntry>() {
@Override
public long expireAfterCreate(@NonNull String s, @NonNull CaffeineEntry caffeineEntry, long l) {
return caffeineEntry.getExpireTime();
}
@Override
public long expireAfterUpdate(@NonNull String s, @NonNull CaffeineEntry caffeineEntry, long l, @NonNegative long l1) {
return caffeineEntry.getExpireTime();
}
@Override
public long expireAfterRead(@NonNull String s, @NonNull CaffeineEntry caffeineEntry, long l, @NonNegative long l1) {
if (caffeineEntry.isAccessFresh()) {
return caffeineEntry.getExpireTime();
}
return l1;
}
}).build();
this.cache = build;
}
/**
* 加入缓存,默认过期时间三十分钟且读后不刷新
*
* @param key
* @param value
*/
public void putIntoCache(String key, Object value) {
putIntoCache(key, value, 30, TimeUnit.MINUTES, false);
}
/**
* 加入缓存,可指定过期时间,默认不刷新
*
* @param key
* @param value
* @param duration
* @param timeUnit
*/
public void putIntoCache(String key, Object value, long duration, TimeUnit timeUnit) {
putIntoCache(key, value, duration, timeUnit, false);
}
/**
* 加入缓存,可指定过期时间、读后是否刷新
*
* @param key
* @param value
* @param duration
* @param timeUnit
* @param accessFresh
*/
public void putIntoCache(String key, Object value, long duration, TimeUnit timeUnit, boolean accessFresh) {
CaffeineEntry caffeineEntry = new CaffeineEntry();
caffeineEntry.setKey(key);
caffeineEntry.setValue(value);
caffeineEntry.setExpireTime(timeUnit.toNanos(duration));
caffeineEntry.setAccessFresh(accessFresh);
cache.put(key, caffeineEntry);
}
/**
* 获取缓存对象
*
* @param key
* @param clazz
* @param <T>
* @return
*/
public <T> T getCacheObject(String key, Class<T> clazz) {
final Object result = getCacheObject(key);
return Objects.nonNull(result) ? clazz.cast(result) : null;
}
/**
* 获取缓存集合
*
* @param key
* @param clazz
* @param <T>
* @return
*/
public <T> List<T> getCacheObjectList(String key, Class<T> clazz) {
final Object result = getCacheObject(key);
if (Objects.nonNull(result)) {
Collection<?> collection = (Collection<?>) result;
List<T> list = new ArrayList<>();
collection.forEach(item -> list.add(clazz.cast(item)));
return list;
}
return null;
}
/**
* 获取基本缓存对象
*
* @param key
* @return
*/
public Object getCacheObject(String key) {
final Object[] value = {null};
Optional.ofNullable(cache.getIfPresent(key))
.ifPresent(item -> {
value[0] = item.getValue();
});
return value[0];
}
@Data
@AllArgsConstructor
@NoArgsConstructor
static class CaffeineEntry {
private String key;
private Object value;
private long expireTime;
private boolean accessFresh;
}
}
在使用时,可以通过SpringBoot配置类对工具进行实例化注册到容器当中,方便在各个业务层当中注入。