@Cacheable的實現原理


如果你用過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的邏輯算是執行完畢。

還有兩點需要注意的就是

  1. 上面的實現是通過proxy的形式實現,那么對象的方法是內部調用(即 this 引用)而不是外部引用,則會導致 proxy失效,也就是注解失效。
  2. 非public方法同上
  3. @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日


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM