Spring的@Cacheable取缓存默认实现
1 概述
在Spring体系中,要实现缓存非常容易,只需要在需要缓存的方法上加上@Cacheable注解即可。这是因为提供了缓存的默认实现,但这个实现不支持缓存过期,也不支持设置缓存总量的大小。需要了解一下这个实现的原理,搞清楚为什么有这些限制,是否有方法能够克服这些局限。
2 实现原理
2.1 取缓存过程
在Spring中,当一个方法加上@Cacheable注解的时候,若这个方法被调用,会被CacheInterceptor拦截器进行拦截,在拦截器里增加先取缓存的操作,只有当缓存取不到的时候,才会访问原方法的逻辑。主要的逻辑在CacheInterceptor的父类CacheAspectSupport实现。
// 代码来源:org.springframework.cache.interceptor.CacheInterceptor
public Object invoke(final MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();CacheOperationInvoker aopAllianceInvoker = () -> {try {return invocation.proceed();}catch (Throwable ex) {throw new CacheOperationInvoker.ThrowableWrapper(ex);}};Object target = invocation.getThis();Assert.state(target != null, "Target must not be null");try {// 1. 执行execute()方法进行取缓存操作,CacheInterceptor继承于CacheAspectSupport,execute()方法由CacheAspectSupport提供return execute(aopAllianceInvoker, target, method, invocation.getArguments());}catch (CacheOperationInvoker.ThrowableWrapper th) {throw th.getOriginal();}
}// 代码来源:org.springframework.cache.interceptor.CacheAspectSupport
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {// 省略部分代码// 2. 从缓存中取数据Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));List<CachePutRequest> cachePutRequests = new ArrayList<>(1);if (cacheHit == null) {collectPutRequests(contexts.get(CacheableOperation.class),CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);}Object cacheValue;Object returnValue;if (cacheHit != null && !hasCachePut(contexts)) {cacheValue = cacheHit.get();returnValue = wrapCacheValue(method, cacheValue);}else {returnValue = invokeOperation(invoker);cacheValue = unwrapReturnValue(returnValue);}// 省略部分代码return returnValue;
}
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {Object result = CacheOperationExpressionEvaluator.NO_RESULT;for (CacheOperationContext context : contexts) {if (isConditionPassing(context, result)) {// 3. 构造缓存keyObject key = generateKey(context, result);// 4. 根据key取缓存对象Cache.ValueWrapper cached = findInCaches(context, key);if (cached != null) {return cached;}else {if (logger.isTraceEnabled()) {logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());}}}}return null;
}
private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {// 5. 遍历缓存,默认的Cache为org.springframework.cache.concurrent.ConcurrentMapCachefor (Cache cache : context.getCaches()) {// 6. 从缓存中取数据,CacheAspectSupport继承于AbstractCacheInvoker,doGet由AbstractCacheInvoker提供Cache.ValueWrapper wrapper = doGet(cache, key);if (wrapper != null) {if (logger.isTraceEnabled()) {logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");}return wrapper;}}return null;
}// 代码来源:org.springframework.cache.interceptor.AbstractCacheInvoker
protected Cache.ValueWrapper doGet(Cache cache, Object key) {try {// 7. 根据key取缓存数据return cache.get(key);}catch (RuntimeException ex) {getErrorHandler().handleCacheGetError(ex, cache, key);return null; }
}从上面代码可以看到,这中间没有留可以扩展的地方,数据缓存在第5步取到的ConcurrentMapCache,该类实现了org.springframework.cache.Cache接口,提供get()、put()、evict()等标准的缓存接口。那这个ConcurrentMapCache是如何来的呢?
2.2 Cache的创建
在默认的实现中,ConcurrentMapCache是在ConcurrentMapCacheManager中创建的,如下面代码:
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);@Overridepublic Cache getCache(String name) {Cache cache = this.cacheMap.get(name);if (cache == null && this.dynamic) {cache = this.cacheMap.computeIfAbsent(name, this::createConcurrentMapCache);}return cache;}protected Cache createConcurrentMapCache(String name) {SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256), isAllowNullValues(), actualSerialization);}// 省略其它代码
}ConcurrentMapCacheManager维护了一个ConcurrentMap,key为缓存的名称,value是ConcurrentMapCache。
在创建ConcurrentMapCache的时候,直接使用的是new ConcurrentMapCache()的方式,这代表着无法通过更换ConcurrentMapCache的实现,这里并没有留扩展的地方。
在创建ConcurrentMapCache的时候,直接new ConcurrentHashMap<>(256),说明实际的缓存数据是存储在ConcurrentHashMap里,ConcurrentHashMap属于无界Map(最大2^30),ConcurrentHashMap也没有为数据设置过期时间的功能。这对于缓存来说,比较危险,无法控制其大小,直到内存不够用而OOM。
2.3 CacheManager的创建
CacheManager后面的部分都无法扩展,则看看CacheManager是从哪里来的。CacheAspectSupport实现了SmartInitializingSingleton接口,Spring对实现了SmartInitializingSingleton接口的类,在创建bean完成之后会触发调用该接口的实现afterSingletonsInstantiated()。
// 源码位置:org.springframework.cache.interceptor.CacheAspectSupport
public void afterSingletonsInstantiated() {if (getCacheResolver() == null) {Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect");try {// 1. 设置CacheManager,这个CacheManager是通过beanFactory取得的,说明前面已经进行bean注册setCacheManager(this.beanFactory.getBean(CacheManager.class));}// 省略部分代码}this.initialized = true;
}
public void setCacheManager(CacheManager cacheManager) {// 2. CacheManager设置到了SimpleCacheResolver里面this.cacheResolver = SingletonSupplier.of(new SimpleCacheResolver(cacheManager));
}// 源码位置:org.springframework.cache.interceptor.CacheAspectSupport.CacheOperationContext
public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {this.metadata = metadata;this.args = extractArgs(metadata.method, args);this.target = target;// 3. 在CacheAspectSupport中构建CacheOperationContext的时候,使用了cacheResolver获取caches// 注意上面获取缓存的时候,是从context取出来的,此处就是context里caches的初始化this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);this.cacheNames = prepareCacheNames(this.caches);
}// 源码位置:org.springframework.cache.interceptor.CacheAspectSupport
protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) {// 4. 通过CacheResolver获取到cachesCollection<? extends Cache> caches = cacheResolver.resolveCaches(context);if (caches.isEmpty()) {throw new IllegalStateException("No cache could be resolved for '" +context.getOperation() + "' using resolver '" + cacheResolver +"'. At least one cache should be provided per cache operation.");}return caches;
}// 源码位置:org.springframework.cache.interceptor.AbstractCacheResolver
// SimpleCacheResolver继承于AbstractCacheResolver,上面代码已经通过SimpleCacheResolver设置CacheManager
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {Collection<String> cacheNames = getCacheNames(context);if (cacheNames == null) {return Collections.emptyList();}Collection<Cache> result = new ArrayList<>(cacheNames.size());for (String cacheName : cacheNames) {// 5. 由CacheManager提供Cache对象Cache cache = getCacheManager().getCache(cacheName);if (cache == null) {throw new IllegalArgumentException("Cannot find cache named '" +cacheName + "' for " + context.getOperation());}result.add(cache);}return result;
}从上面代码可以看出,afterSingletonsInstantiated()实现的时候,设置了一个已经注册的CacheManager实例,由这个实例给CacheOperationContext提供Cache对象。那这个CacheManager实例是在哪里注入的呢?
// 源码位置:org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {@BeanConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers) {ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();List<String> cacheNames = cacheProperties.getCacheNames();if (!cacheNames.isEmpty()) {cacheManager.setCacheNames(cacheNames);}return cacheManagerCustomizers.customize(cacheManager);}
}从上面代码可以看到,默认的ConcurrentMapCacheManager是在SimpleCacheConfiguration注册成bean的,由它来提供缓存的管理。
既然这个bean是注入的,上面还有@ConditionalOnMissingBean(CacheManager.class),那就代表着可以通过自定义一个CacheManager来替换掉默认的实现。只替换CacheManager的实现,不需要修改@Cacheable的相关实现,就能够增加缓存的功能,如缓存过期时间、缓存大小限制等。
3 小结
Spring提供的@Cacheable的默认实现不支持缓存过期时间、缓存大小限制,是因为用了ConcurrentMapCacheManager进行管理缓存,该manager是采用ConcurrentHashMap来存储缓存数据的,无法设置缓存过期时间,也不能设置容量大小。但Spring对ConcurrentMapCacheManager采用的是注入的方式,可以通过自定义CacheManager来更换默认的ConcurrentMapCacheManager,从而达到增加缓存功能的目的。
