如果你用過Spring Cache,你一定對這種配置和代碼不陌生:
<cache:annotation-driven cache-manager=
"cacheManager"
proxy-target-
class
=
"true"
order=
"1"
/>
|
@Cacheable
(value =
"3600"
, key =
"i'm a cache key"
)
public
List<Object> getData(){}
|
上面兩段代碼,xml是啟用Cache Annotation注解並注冊一個cacheManager,第二段代碼在getData的時候會先去緩存里取,如果緩存沒有再執行getData的真實邏輯。
那么今天的“走進科學”講的就是Spring是怎么做到僅僅一段xml配置和一個注解就實現方法級別的自動緩存。
我們滋道Spring最牛逼的地方就在於IOC容器對於bean的管理,可以說是Spring牛逼的基石,那么畫風一換,到了我們今天討論的起點就是Spring在啟動時對xml里面的各種標簽進行解析,比如對應<cache:annotation-driven>標簽,負責解析的就是AnnotationDrivenCacheBeanDefinitionParser.parse方法,代碼看起來很簡單,根據mode屬性注冊Advisor Component:
我們今天先看默認mode=proxy的情況,進入方法,發現方法里面注冊了三個Bean到Context里面,分別是CacheOperationSource、CacheInterceptor和BeanFactoryCacheOperationSourceAdvisor。
熟悉AOP原理的看到Interceptor和Advisor一般都會明白大半了,並且他們共同都有一個屬性cacheOperationSources,實現類是org.springframework.cache.annotation.AnnotationCacheOperationSource。
下面我們先來喵兩眼這兩個類,先看BeanFactoryCacheOperationSourceAdvisor,里面有一個叫CacheOperationSourcePointcut的pointcut,用來匹配方法是否需要走攔截器。通過調用之前注入進去的cacheOperationSources.getCacheOperations獲取CacheOperation,代碼如下:
這樣只有被CacheOperationSourcePointcut匹配的方法才會被攔截,並且通過attributeCache做了緩存。
再來看CacheInterceptor類,先看一眼繼承結構:
這個類很簡單,只是重寫了MethodInterceptor的invoke方法:
下一步是調用CacheAspectSupport
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,
new
CacheOperationContexts(operations, method, args, target, targetClass));
}
}
return
invoker.invoke();
}
|
其中根據 getCacheOperations獲得cacheOperations后調用的execute是關鍵,其中getCacheOperationSource即是之前說到的bean里面的cacheOperationSources,也就是org.springframework.cache.annotation.AnnotationCacheOperationSource,它負責三個標簽的調用:@Cacheable、@CachePut和@CacheEvict。
下面嘍一眼execute方法的代碼:
private
Object execute(CacheOperationInvoker invoker, CacheOperationContexts contexts) {
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.
class
),
true
, ExpressionEvaluator.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
), ExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Cache.ValueWrapper result =
null
;
// If there are no put requests, just use the cache hit
if
(cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
result = cacheHit;
}
// Invoke the method if don't have a cache hit
if
(result ==
null
) {
result =
new
SimpleValueWrapper(invokeOperation(invoker));
}
// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.
class
), result.get(), cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for
(CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(result.get());
}
// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.
class
),
false
, result.get());
return
result.get();
}
|
這段代碼看起來還是比較“簡單”的,也是Spring Cache邏輯的核心實現了吧,根據注解執行了方法前和方法后需要的緩存操作,注意對於失效的操作分為early evictions和late evictions,對應標簽@CacheEvict中的beforeInvocation屬性。自此,Spring cache的邏輯算是執行完畢。
還有兩點需要注意的就是
- 上面的實現是通過proxy的形式實現,那么對象的方法是內部調用(即 this 引用)而不是外部引用,則會導致 proxy失效,也就是注解失效。
- 非public方法同上
- @CacheEvict標簽不會對拋出異常的方法的緩存進行清空,通過將beforeInvocation設置為true,即在方法執行前
最后的話:
本篇文章並沒有講Spring的AOP實現原理以及Spring Cache的更多細節。
參考:
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html
https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/
http://www.cnblogs.com/chanedi/p/4552555.html
原文發表於2015年10月16日