cheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressioneva luator.NO_RESULT, cachePutRequests);
}
Object cacheva lue;
Object returnValue;
if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheva lue = cacheHit.get();
returnValue = wrapCacheva lue(method, cacheva lue);
}
else {
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheva lue = unwrapReturnValue(returnValue);
}
// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheva lue, cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheva lue);
}
// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheva lue);
return returnValue;
}
我们的方法没有使用同步,走到 processCacheEvicts 方法。
private void processCacheEvicts(Collection<CacheOperationContext> contexts, boolean beforeInvocation, Object result) {
for (CacheOperationContext context : contexts) {
CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
performCacheEvict(context, operation, result);
}
}
}
注意这个方法传入的 beforeInvocation 参数是 true,说明是方法执行前进行的操作,这里是取出 CacheEvictOperation,operation.isBeforeInvocation(),调用下面方法:
private void performCacheEvict(CacheOperationContext context, CacheEvictOperation operation, Object result) {
Object key = null;
for (Cache cache : context.getCaches()) {
if (operation.isCacheWide()) {
logInvalidating(context, operation, null);
doClear(cache);
}
else {
if (key == null) {
key = context.generateKey(result);
}
logInvalidating(context, operation, key);
doEvict(cache, key);
}
}
}
这里需要注意了,operation 中有个参数 cacheWide,如果使用这个参数并设置为true,则在缓存失效时,会调用 clear 方法进行全部缓存的清理,否则只对当前 key 进行 evict 操作。本文中,doEvict() 最终会调用到 ConcurrentMapCache的evict(Object key) 方法,将 key 缓存失效。
回到 execute 方法,走到 Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); 这一步,这里会根据当前方法是否有 CacheableOperation 注解,进行缓存的查询,如果没有命中缓存,则会调用方法拦截器 CacheInterceptor 的 proceed 方法,进行原方法的调用,得到缓存 key 对应的 value,然后通过 cachePutRequest.apply(cacheva lue) 设置缓存。
public void apply(Object result) {
if (this.context.canPutToCache(result)) {
for (Cache cache : this.context.getCaches()) {
doPut(cache, this.key, result);
}
}
}
doPut() 方法最终对调用到 ConcurrentMapCache 的 put 方法,完成缓存的设置工作。
最后 execute 方法还有最后一步 processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheva lue); 处理针对执行方法后缓存失效的注解策略。
优缺点
优点
- 方便快捷高效,可直接嵌入多个现有的 cache 实现,简写了很多代码,可观性非常强。
缺点
- 内部调用,非 public 方法上使用注解,会导致缓存无效。由于 SpringCache 是基于 Spring AOP 的动态代理实现,由于代理本身的问题,当同一个类中调用另一个方法,会导致另一个方法的缓存不能使用,这个在编码上需要注意,避免在同一