前兩篇我們講了 Guava 和 JetCache,它們都是緩存的具體實現(xiàn),今天給大家分析一下 Spring 框架本身對這些緩存具體實現(xiàn)的支持和融合。使用 Spring Cache 將大大的減少我們的Spring項目中緩存使用的復(fù)雜度,提高代碼可讀性。本文將從以下幾個方面來認識Spring Cache框架。
背景
SpringCache 產(chǎn)生的背景其實與Spring產(chǎn)生的背景有點類似。由于 Java EE 系統(tǒng)框架臃腫、低效,代碼可觀性低,對象創(chuàng)建和依賴關(guān)系復(fù)雜, Spring 框架出來了,目前基本上所有的Java后臺項目都離不開 Spring 或 SpringBoot (對 Spring 的進一步簡化)。現(xiàn)在項目面臨高并發(fā)的問題越來越多,各類緩存的應(yīng)用也增多,那么在通用的 Spring 框架上,就需要有一種更加便捷簡單的方式,來完成緩存的支持,就這樣 SpringCache就出現(xiàn)了。
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() {
@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 cachePutRequests = new LinkedList();
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;
}
內(nèi)部調(diào)用,非 public 方法上使用注解,會導(dǎo)致緩存無效。由于 SpringCache 是基于 Spring AOP 的動態(tài)代理實現(xiàn),由于代理本身的問題,當同一個類中調(diào)用另一個方法,會導(dǎo)致另一個方法的緩存不能使用,這個在編碼上需要注意,避免在同一個類中這樣調(diào)用。如果非要這樣做,可以通過再次代理調(diào)用,如 ((Category)AopContext.currentProxy()).get(category) 這樣避免緩存無效。