Spring cache是一個快取API層,封裝了對多種快取的通用操作,可以藉助註解方便地為程式新增快取功能。
常見的註解有@Cacheable、@CachePut、@CacheEvict,有沒有想過背後的原理是什麼?樓主帶著疑問,閱讀完Spring cache的原始碼後,做一個簡要總結。
先說結論,核心邏輯在CacheAspectSupport類,封裝了所有的快取操作的主體邏輯,下面詳細介紹。
題外話:如何閱讀開原始碼?
有2種方法,可以結合起來使用:
- 靜態程式碼閱讀:查詢關鍵類、方法的usage之處,熟練使用find usages功能,找到所有相關的類、方法,靜態分析核心邏輯的執行過程,一步步追根問底,直至建立全貌
- 執行時debug:在關鍵方法上加上斷點,並且寫一個單元測試呼叫類庫/框架,熟練使用step into/step over/resume來動態分析程式碼的執行過程
核心類圖
如圖所示,可以分成以下幾類class:
- Cache、CacheManager:Cache抽象了快取的通用操作,如get、put,而CacheManager是Cache的集合,之所以需要多個Cache物件,是因為需要多種快取失效時間、快取條目上限等
- CacheInterceptor、CacheAspectSupport、AbstractCacheInvoker:CacheInterceptor是一個AOP方法攔截器,在方法前後做額外的邏輯,也即查詢快取、寫入快取等,它繼承了CacheAspectSupport(快取操作的主體邏輯)、AbstractCacheInvoker(封裝了對Cache的讀寫)
- CacheOperation、AnnotationCacheOperationSource、SpringCacheAnnotationParser:CacheOperation定義了快取操作的快取名字、快取key、快取條件condition、CacheManager等,AnnotationCacheOperationSource是一個獲取快取註解對應CacheOperation的類,而SpringCacheAnnotationParser是真正解析註解的類,解析後會封裝成CacheOperation集合供AnnotationCacheOperationSource查詢
原始碼分析(帶註釋解釋)
下面對Spring cache原始碼做分析,帶註釋解釋,只摘錄核心程式碼片段。
1、解析註解
首先看看註解是如何解析的。註解只是一個標記,要讓它真正工作起來,需要對註解做解析操作,並且還要有對應的實際邏輯。
SpringCacheAnnotationParser:負責解析註解,返回CacheOperation集合
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
// 解析類級別的快取註解
@Override
public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
DefaultCacheConfig defaultConfig = getDefaultCacheConfig(type);
return parseCacheAnnotations(defaultConfig, type);
}
// 解析方法級別的快取註解
@Override
public Collection<CacheOperation> parseCacheAnnotations(Method method) {
DefaultCacheConfig defaultConfig = getDefaultCacheConfig(method.getDeclaringClass());
return parseCacheAnnotations(defaultConfig, method);
}
// 解析快取註解
private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
Collection<CacheOperation> ops = null;
// 解析@Cacheable註解
Collection<Cacheable> cacheables = AnnotatedElementUtils.getAllMergedAnnotations(ae, Cacheable.class);
if (!cacheables.isEmpty()) {
ops = lazyInit(ops);
for (Cacheable cacheable : cacheables) {
ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
}
}
// 解析@CacheEvict註解
Collection<CacheEvict> evicts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CacheEvict.class);
if (!evicts.isEmpty()) {
ops = lazyInit(ops);
for (CacheEvict evict : evicts) {
ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
}
}
// 解析@CachePut註解
Collection<CachePut> puts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CachePut.class);
if (!puts.isEmpty()) {
ops = lazyInit(ops);
for (CachePut put : puts) {
ops.add(parsePutAnnotation(ae, cachingConfig, put));
}
}
// 解析@Caching註解
Collection<Caching> cachings = AnnotatedElementUtils.getAllMergedAnnotations(ae, Caching.class);
if (!cachings.isEmpty()) {
ops = lazyInit(ops);
for (Caching caching : cachings) {
Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
if (cachingOps != null) {
ops.addAll(cachingOps);
}
}
}
return ops;
}
AnnotationCacheOperationSource:呼叫SpringCacheAnnotationParser獲取註解對應CacheOperation
public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {
// 查詢類級別的CacheOperation列表
@Override
protected Collection<CacheOperation> findCacheOperations(final Class<?> clazz) {
return determineCacheOperations(new CacheOperationProvider() {
@Override
public Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser) {
return parser.parseCacheAnnotations(clazz);
}
});
}
// 查詢方法級別的CacheOperation列表
@Override
protected Collection<CacheOperation> findCacheOperations(final Method method) {
return determineCacheOperations(new CacheOperationProvider() {
@Override
public Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser) {
return parser.parseCacheAnnotations(method);
}
});
}
}
AbstractFallbackCacheOperationSource:AnnotationCacheOperationSource的父類,實現了獲取CacheOperation的通用邏輯
public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource {
/**
* Cache of CacheOperations, keyed by method on a specific target class.
* <p>As this base class is not marked Serializable, the cache will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
private final Map<Object, Collection<CacheOperation>> attributeCache =
new ConcurrentHashMap<Object, Collection<CacheOperation>>(1024);
// 根據Method、Class反射資訊,獲取對應的CacheOperation列表
@Override
public Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
return null;
}
Object cacheKey = getCacheKey(method, targetClass);
Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);
// 因解析反射資訊較耗時,所以用map快取,避免重複計算
// 如在map裡已記錄,直接返回
if (cached != null) {
return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
}
// 否則做一次計算,然後寫入map
else {
Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
if (cacheOps != null) {
if (logger.isDebugEnabled()) {
logger.debug("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
}
this.attributeCache.put(cacheKey, cacheOps);
}
else {
this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
}
return cacheOps;
}
}
// 計算快取操作列表,優先用target代理類的方法上的註解,如果不存在則其次用target代理類,再次用原始類的方法,最後用原始類
private Collection<CacheOperation> computeCacheOperations(Method method, Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
// If we are dealing with method with generic parameters, find the original method.
specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
// 呼叫findCacheOperations(由子類AnnotationCacheOperationSource實現),最終通過SpringCacheAnnotationParser來解析
// First try is the method in the target class.
Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
if (opDef != null) {
return opDef;
}
// Second try is the caching operation on the target class.
opDef = findCacheOperations(specificMethod.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
opDef = findCacheOperations(method);
if (opDef != null) {
return opDef;
}
// Last fallback is the class of the original method.
opDef = findCacheOperations(method.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
}
return null;
}
2、邏輯執行
以@Cacheable背後的邏輯為例。預期是先查快取,如果快取命中了就直接使用快取值,否則執行業務邏輯,並把結果寫入快取。
ProxyCachingConfiguration:是一個配置類,用於生成CacheInterceptor類和CacheOperationSource類的Spring bean
CacheInterceptor:是一個AOP方法攔截器,它通過CacheOperationSource獲取第1步解析註解的CacheOperation結果(如快取名字、快取key、condition條件),本質上是攔截原始方法的執行,在之前、之後增加邏輯
// 核心類,快取攔截器
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
// 攔截原始方法的執行,在之前、之後增加邏輯
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
// 封裝原始方法的執行到一個回撥介面,便於後續呼叫
CacheOperationInvoker aopAllianceInvoker = new CacheOperationInvoker() {
@Override
public Object invoke() {
try {
// 原始方法的執行
return invocation.proceed();
}
catch (Throwable ex) {
throw new ThrowableWrapper(ex);
}
}
};
try {
// 呼叫父類CacheAspectSupport的方法
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
}
CacheAspectSupport:快取切面支援類,是CacheInterceptor的父類,封裝了所有的快取操作的主體邏輯
主要流程如下:
- 通過CacheOperationSource,獲取所有的CacheOperation列表
- 如果有@CacheEvict註解、並且標記為在呼叫前執行,則做刪除/清空快取的操作
- 如果有@Cacheable註解,查詢快取
- 如果快取未命中(查詢結果為null),則新增到cachePutRequests,後續執行原始方法後會寫入快取
- 快取命中時,使用快取值作為結果;快取未命中、或有@CachePut註解時,需要呼叫原始方法,使用原始方法的返回值作為結果
- 如果有@CachePut註解,則新增到cachePutRequests
- 如果快取未命中,則把查詢結果值寫入快取;如果有@CachePut註解,也把方法執行結果寫入快取
- 如果有@CacheEvict註解、並且標記為在呼叫後執行,則做刪除/清空快取的操作
// 核心類,快取切面支援類,封裝了所有的快取操作的主體邏輯
public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
// CacheInterceptor調父類的該方法
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);
// 通過CacheOperationSource,獲取所有的CacheOperation列表
Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
// 繼續調一個private的execute方法執行
return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
// 如果spring bean未初始化完成,則直接呼叫原始方法。相當於原始方法沒有快取功能。
return invoker.invoke();
}
private的execute方法
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);
}
}
// 如果有@CacheEvict註解、並且標記為在呼叫前執行,則做刪除/清空快取的操作
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// 如果有@Cacheable註解,查詢快取
// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// 如果快取未命中(查詢結果為null),則新增到cachePutRequests,後續執行原始方法後會寫入快取
// 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 {
// 快取未命中、或有@CachePut註解的情況,需要呼叫原始方法
// Invoke the method if we don't have a cache hit
// 呼叫原始方法,得到結果值
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// 如果有@CachePut註解,則新增到cachePutRequests
// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// 如果快取未命中,則把查詢結果值寫入快取;如果有@CachePut註解,也把方法執行結果寫入快取
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// 如果有@CacheEvict註解、並且標記為在呼叫後執行,則做刪除/清空快取的操作
// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
// 如果滿足condition條件,才查詢快取
if (isConditionPassing(context, result)) {
// 生成快取key,如果註解中指定了key,則按照Spring表示式解析,否則使用KeyGenerator類生成
Object key = generateKey(context, result);
// 根據快取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) {
for (Cache cache : context.getCaches()) {
// 呼叫父類AbstractCacheInvoker的doGet方法,查詢快取
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;
}
AbstractCacheInvoker:CacheAspectSupport的父類,封裝了最終查詢Cache介面的邏輯
public abstract class AbstractCacheInvoker {
// 最終查詢快取的方法
protected Cache.ValueWrapper doGet(Cache cache, Object key) {
try {
// 呼叫Spring Cache介面的查詢方法
return cache.get(key);
}
catch (RuntimeException ex) {
getErrorHandler().handleCacheGetError(ex, cache, key);
return null; // If the exception is handled, return a cache miss
}
}
}