Spring Cache (5) - 核心部分 CacheAspectSupport

上上篇还有一章 CacheInterceptor 的基类 CacheAspectSupport 没有细讲,这篇详细讲解一下。

CacheAspectSupport 详解

先看看 CacheInterceptor 的继承关系:

CacheInterceptor继承关系

CacheAspectSupport 又继承自 AbstractCacheInvoker,所以我们从 AbstractCacheInvoker 开始看起:

AbstractCacheInvoker

先看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public abstract class AbstractCacheInvoker {
private CacheErrorHandler errorHandler;

protected AbstractCacheInvoker() {
this(new SimpleCacheErrorHandler());
}

protected AbstractCacheInvoker(CacheErrorHandler errorHandler) {
Assert.notNull(errorHandler, "ErrorHandler must not be null");
this.errorHandler = errorHandler;
}

public void setErrorHandler(CacheErrorHandler errorHandler) {
this.errorHandler = errorHandler;
}

public CacheErrorHandler getErrorHandler() {
return this.errorHandler;
}

protected Cache.ValueWrapper doGet(Cache cache, Object key) {
try {
return cache.get(key);
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheGetError(ex, cache, key);
return null; // If the exception is handled, return a cache miss
}
}

protected void doPut(Cache cache, Object key, Object result) {
try {
cache.put(key, result);
}
catch (RuntimeException ex) {
getErrorHandler().handleCachePutError(ex, cache, key, result);
}
}

protected void doEvict(Cache cache, Object key) {
try {
cache.evict(key);
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheEvictError(ex, cache, key);
}
}

protected void doClear(Cache cache) {
try {
cache.clear();
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheClearError(ex, cache);
}
}
}

errorHandler 默认使用 SimpleCacheErrorHandler 实现,即直接抛出所有异常。

提供了四个 doXXX 方法(XXX = Get/Put/Evict/Clear)分别调用了 cache 的四个方法,并交给 errorHandler 处理异常。

AbstractCacheInvoker 小结

AbstractCacheInvoker 的作用就是在最基本的 cache 的基本操作上包了一层异常处理。

CacheAspectSupport

CacheAspectSupport 的 Optional 支持

先说此处有个有意思的地方。CacheAspectSupport 支持对 Optional 拆包。但由于 Optional 是 Java 8 才有的新特性,为了避免对 Java 8 的硬依赖,对 Optional 类用了一个特殊的加载方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private static Class<?> javaUtilOptionalClass = null;

static {
try {
javaUtilOptionalClass =
ClassUtils.forName("java.util.Optional", CacheAspectSupport.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
// Java 8 not available - Optional references simply not supported then.
}
}

@UsesJava8
private static class OptionalUnwrapper {

public static Object unwrap(Object optionalObject) {
Optional<?> optional = (Optional<?>) optionalObject;
if (!optional.isPresent()) {
return null;
}
Object result = optional.get();
Assert.isTrue(!(result instanceof Optional), "Multi-level Optional usage not supported");
return result;
}

public static Object wrap(Object value) {
return Optional.ofNullable(value);
}
}

CacheAspectSupport 的属性检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

@Override
public void afterPropertiesSet() {
Assert.state(getCacheOperationSource() != null, "The 'cacheOperationSources' property is required: " +
"If there are no cacheable methods, then don't use a cache aspect.");
Assert.state(getErrorHandler() != null, "The 'errorHandler' property is required");
}

@Override
public void afterSingletonsInstantiated() {
if (getCacheResolver() == null) {
// Lazily initialize cache resolver via default cache manager...
try {
setCacheManager(this.beanFactory.getBean(CacheManager.class));
}
catch (NoUniqueBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " +
"CacheManager found. Mark one as primary or declare a specific CacheManager to use.");
}
catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no bean of type CacheManager found. " +
"Register a CacheManager bean or remove the @EnableCaching annotation from your configuration.");
}
}
this.initialized = true;
}

简而言之就是:cacheOperationSources 和 errorHandler 必须设置、在没有定义 cacheResolver 的情况下会从容器取 cacheManager。

CacheAspectSupport 的核心代码

1
2
3
4
5
6
7
8
9
10
11
12
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
if (this.initialized) {
Class<?> targetClass = getTargetClass(target);
Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
}
}

return invoker.invoke();
}

首先由 CacheOperationSource 从 method 和 targetClass 的注解生成对应的 CacheOperation 集合,然后将操作、方法、参数等打包整理成一个缓存操作的上下文 CacheOperationContexts。

接下来就是 execute 的主逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// Special handling of synchronized invocation
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<Object>() {
@Override
public Object call() throws Exception {
return unwrapReturnValue(invokeOperation(invoker));
}
}));
}
catch (Cache.ValueRetrievalException ex) {
// The invoker wraps any Throwable in a ThrowableWrapper instance so we
// can just make sure that one bubbles up the stack.
throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
}
}
else {
// No caching required, only call the underlying method
return invokeOperation(invoker);
}
}


// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);

// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}

Object cacheValue;
Object returnValue;

if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}

// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}

// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

return returnValue;
}

首先区分是否有同步标记,同步标记是在构造 CacheOperationContexts 时由 determineSyncFlag(method) 方法确定的。只有 Cacheable 操作有 sync 标记,且只能允许有这一个 Cache 操作,且只允许有一个 Cache,且不支持 unless 注解。

所以对于同步调用而言,取出这个 CacheableOperation,通过 isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT) 判断是否符合条件,符合条件则进行缓存读写操作(尝试从缓存获取,没有则执行函数并写入缓存),否则直接执行函数。对于同步调用,此处已经结束了。

对于没有同步标记的情况,首先由 processCacheEvicts 执行调用前的缓存清除操作:

1
2
3
4
5
6
7
8
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);
}
}
}

关于 performCacheEvict 的细节有机会细讲。

接下来,由 findCachedItem(contexts.get(CacheableOperation.class)) 获取 CacheableOperation 命中的缓存。

如果未命中,则聚合之后 Cacheable 操作需要的 put 请求,聚合函数如下:

1
2
3
4
5
6
7
8
9
10
private void collectPutRequests(Collection<CacheOperationContext> contexts,
Object result, Collection<CachePutRequest> putRequests) {

for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
putRequests.add(new CachePutRequest(context, key));
}
}
}

接下来看 cacheHit 非空,如果也没有 CachePut 操作,那么可以直接取 cacheHit 作为返回值。否则需要调用方法作为返回值。

再将显式注解的 CachePutOperation 聚合到之前的 Put 操作集合中,依次调用 cachePutRequest.apply(cacheValue)

最后执行函数后清空缓存的 CacheEvicts 操作。然后返回之前的返回值。

没有仔细讲的细节

在这个过程中,对返回值和异常穿插 wrap 和 unwrap 的处理,支持了有 Optional 的情况。

关于例如 generateKey 这些到的涉及表达式 Evaluation 的部分下章细讲。