Caffeine是使用Java8对Guava缓存的重写版本,在Spring Boot 2.0中将取代Guava。如果出现Caffeine,CaffeineCacheManager将会自动配置。caffeine是目前最高性能的java本地缓存库。
github:
https://github.com/ben-manes/caffeine
String caffeineSpec = "initialCapacity=50,maximumSize=500,refreshAfterWrite=6s";
CaffeineSpec spec = CaffeineSpec.parse(caffeineSpec);
Caffeine caffeine = Caffeine.from(spec);
Cache manualCache = caffeine.build();
// 通过key查询value,没有返回null
manualCache.getIfPresent("key");
// 通过key查询value,没有根据传入的function初始化这个key
manualCache.get("key", k -> load(k));
// 将key及对应的value放入缓存
manualCache.put("key", "value");
// 删除key对应的mapping
manualCache.invalidate("key");
通过手动加载你可以显式的去查询、更新、删除一个缓存,Caffeine 的创建方式除了上述方式,还可通过以下方式创建
Caffeine caffeine = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES)
.maximumSize(100)
String caffeineSpec = "initialCapacity=50,maximumSize=500,refreshAfterWrite=6s";
CaffeineSpec spec = CaffeineSpec.parse(caffeineSpec);
Caffeine caffeine = Caffeine.from(spec);
// CacheLoader cacheLoader = key -> load(key);
CacheLoader cacheLoader = new CacheLoader() {
@CheckForNull
@Override
public Object load(@Nonnull Object key) throws Exception {
return load(key);
}
};
LoadingCache loadingCache = caffeine.build(cacheLoader);
// 通过key查询value,没有则调用load方法初始化这个key
loadingCache.get("key");
List keyList = Arrays.asList("key1", "key2");
loadingCache.getAll(keyList);
LoadingCache 通过指定一个 CacheLoader 来构建一个之前不存在的缓存,通过get方法获取缓存时调用load方法来初始化,我们也可以通过getAll批量获取缓存,默认情况下,getAll将会对缓存中没有值的key分别调用load方法,通过重写CacheLoader 中的loadAll方法提高效率。
String caffeineSpec = "initialCapacity=50,maximumSize=500,refreshAfterWrite=6s";
CaffeineSpec spec = CaffeineSpec.parse(caffeineSpec);
Caffeine caffeine = Caffeine.from(spec);
// CacheLoader cacheLoader = key -> load(key);
CacheLoader cacheLoader = new CacheLoader() {
@CheckForNull
@Override
public Object load(@Nonnull Object key) throws Exception {
return load(key);
}
};
AsyncCacheLoader asyncCacheLoader = new AsyncCacheLoader() {
@Nonnull
@Override
public CompletableFuture asyncLoad(@Nonnull Object key, @Nonnull Executor executor) {
return asyncLoad(key);
}
};
AsyncLoadingCache asyncLoadingCache = caffeine.buildAsync(cacheLoader);
// 通过key查询value,没有则调用load方法初始化这个key
asyncLoadingCache.get("key").thenAccept(value -> handle(value));
List keyList = Arrays.asList("key1", "key2");
asyncLoadingCache.getAll(keyList).thenAccept(value -> handle(value));
AsyncLoadingCache 与 LoadingCache 是两个完全独立的接口,caffeine通过调用buildAsync来创建异步的cache,buildAsync有两个重载的方法
CacheLoader 继承自 AsyncCacheLoader,两种cacheLoader的用法暂时还没有比较清晰的认识,异步加载使用Executor去调用方法并返回一个CompletableFuture,我们拿到这个CompletableFuture可以对其做我们需要的操作。
异步加载默认使用ForkJoinPool.commonPool()来执行异步线程,我们可以通过Caffeine.executor(Executor) 方法来替换线程池。
CacheLoader cacheLoader = new CacheLoader() {
@CheckForNull
@Override
public Object load(@Nonnull Object key) throws Exception {
return load(key);
}
};
// 根据缓存的数量进行驱逐
Caffeine.newBuilder().maximumSize(10).build(cacheLoader);
// 根据权重进行驱逐
Caffeine.newBuilder().maximumWeight(10)
.weigher((key, value) -> Integer.valueOf(key.toString()))
.build(cacheLoader);
基于大小的回收策略分为两种:
// 访问后过期
Caffeine.newBuilder()
.expireAfterAccess(5, TimeUnit.SECONDS)
.build(cacheLoader);
// 写入后过期
Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS)
.build(cacheLoader);
//自定义过期策略
Caffeine.newBuilder().expireAfter(new Expiry
基于时间的回收策略分为三种:
Caffeine.newBuilder()
.weakKeys()
.weakValues()
.build(cacheLoader);
关于强引用、软引用、弱引用、虚引用的详细描述可参考《深入理解jvm虚拟机》这本书,在caffeine中的应用就是你可以指定key、value为软引用,在jvm内存不足时进行回收。
CacheLoader cacheLoader = new CacheLoader() {
@CheckForNull
@Override
public Object load(@Nonnull Object key) throws Exception {
return load(key);
}
@CheckForNull
@Override
public Object reload(@Nonnull Object key, @Nonnull Object oldValue) throws Exception {
return load(key);
}
};
Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.expireAfterAccess(10, TimeUnit.SECONDS)
.build(cacheLoader);
Caffeine.newBuilder()
.executor(new ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<>()));
我们可以通过expireAfterWrite跟expireAfterAccess来指定刷新时机,当两个参数同时指定时,数据将在具备刷新条件时才去刷新。
caffeine的刷新通过异步调用CacheLoader接口中的reload方法来实现,reload方法的默认实现是通过调用load方法来刷新,当然我们也可以通过重写reload方法来自定义刷新逻辑。
由于刷新是通过ForkJoinPool.commonPool()来异步调用,所以触发刷新的线程会直接拿到旧数据返回,我们可以使用executor指定的线程池替换ForkJoinPool.commonPool()。
org.springframework.boot
spring-boot-starter-cache
com.github.ben-manes.caffeine
caffeine
2.6.2
@Configuration
@EnableCaching
public class CaffeineConfig {
private static final Logger logger = LoggerFactory.getLogger(CaffeineConfig.class);
@Autowired
private ConfigureParameter configureParameter;
/**
* caffeine CacheManager
* @return
*/
@Bean("caffeine")
public CacheManager cacheManager() {
String caffeineSpec = configureParameter.getCaffeineSpec();
CaffeineSpec spec = CaffeineSpec.parse(caffeineSpec);
Caffeine caffeine = Caffeine.from(spec);
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(caffeine);
cacheManager.setAllowNullValues(false);
CacheLoader
在上例中我们通过caffeineSpec来构建caffeine对象,并且CacheLoade中的load方法我们直接返回了一个null,这是为了让spring去调用原方法,来初始化caffeine缓存,这个在之后的源码分析中会提到,我们并没有去重写reload方法,这时候默认去调用load方法,而load返回null,所以注意在异步刷新的时候caffeine会把这个null放置到caffeine缓存中,在caffeine中并没有找到控制value是否可为null的配置,但是spring为我们提供了一个方法setAllowNullValues,如果为false不允许value为null,则在value为null时会抛出一个异常。
我们也可以通过SimpleCacheManage的方式来让spring管理caffeine
/**
* caffeine CacheManager
* @return
*/
@Bean("caffeine")
public CacheManager cacheManager() {
String groupInfo = Constant.GROUP_INFO;
CacheLoader cacheLoader = cacheLoaderContext.getCacheLoader(groupInfo);
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
List cacheList = new ArrayList<>();
String caffeineSpec = configureParameter.getCaffeineSpec();
CaffeineSpec spec = CaffeineSpec.parse(caffeineSpec);
Caffeine caffeine = Caffeine.from(spec);
CaffeineCache caffeineCache = new CaffeineCache(groupInfo, caffeine.build(cacheLoader) , false);
cacheList.add(caffeineCache);
simpleCacheManager.setCaches(cacheList);
return simpleCacheManager;
}
CaffeineCache构造器的第三个参数是allowNullValues,不过这个参数我测试并没有限制到value是否允许为null
@Cacheable(cacheNames = "cahceName", key = "#key", cacheManager = "caffeine", unless = "#result == null")
我们可以使用spring提供的@Cacheable、@CachePut、@CacheEvict等注解来方便的使用caffeine缓存。
如果使用了多个cahce,比如redis、caffeine等,必须指定某一个CacheManage为@primary,在@Cacheable注解中没指定cacheManager 则使用标记为primary的那个。
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 是否异步 sync = true/false
if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try {
return wrapCacheValue(method, cache.get(key, new Callable
CacheAspectSupport是spring提供的与三方cache整合的一个重要类,若想了解更详细的底层实现可以去阅读下源码。