亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

深入理解Spring Cache框架

2024-07-14 08:42:54
字體:
來源:轉載
供稿:網友

本文是緩存系列第三篇,前兩篇分別介紹了 Guava 和 JetCache。

前兩篇我們講了 Guava 和 JetCache,它們都是緩存的具體實現,今天給大家分析一下 Spring 框架本身對這些緩存具體實現的支持和融合。使用 Spring Cache 將大大的減少我們的Spring項目中緩存使用的復雜度,提高代碼可讀性。本文將從以下幾個方面來認識Spring Cache框架。

背景

SpringCache 產生的背景其實與Spring產生的背景有點類似。由于 Java EE 系統框架臃腫、低效,代碼可觀性低,對象創建和依賴關系復雜, Spring 框架出來了,目前基本上所有的Java后臺項目都離不開 Spring 或 SpringBoot (對 Spring 的進一步簡化)。現在項目面臨高并發的問題越來越多,各類緩存的應用也增多,那么在通用的 Spring 框架上,就需要有一種更加便捷簡單的方式,來完成緩存的支持,就這樣 SpringCache就出現了。

不過首先我們需要明白的一點是,SpringCache 并非某一種 Cache 實現的技術,SpringCache 是一種緩存實現的通用技術,基于 Spring 提供的 Cache 框架,讓開發者更容易將自己的緩存實現高效便捷的嵌入到自己的項目中。當然,SpringCache 也提供了本身的簡單實現 NoOpCacheManager、ConcurrentMapCacheManager 等。通過 SpringCache,可以快速嵌入自己的Cache實現。

用法

源碼已分享至Github: https://github.com/zhuzhenke/common-caches

注意點:

1、開啟 EnableCaching 注解,默認沒有開啟 Cache。

2、配置 CacheManager。

@Bean@Qualifier("concurrentMapCacheManager")@PrimaryConcurrentMapCacheManager concurrentMapCacheManager() {  return new ConcurrentMapCacheManager();}

這里使用了 @Primary 和 @Qualifier 注解,@Qualifier 注解是給這個 Bean 加一個名字,用于同一個接口 Bean 的多個實現時,指定當前 Bean 的名字,也就意味著 CacheManager 可以配置多個,并且在不同的方法場景下使用。@Primary 注解是當接口 Bean 有多個時,優先注入當前 Bean 。

現在拿 CategoryService 實現來分析。

public class CategoryService {  @Caching(evict = {@CacheEvict(value = CategoryCacheConstants.CATEGORY_DOMAIN,      key = "#category.getCategoryCacheKey()",      beforeInvocation = true)})  public int add(Category category) {    System.out.println("模擬進行數據庫交互操作......");    System.out.println("Cache became invalid,value:" + CategoryCacheConstants.CATEGORY_DOMAIN        + ",key:" + category.getCategoryCacheKey());    return 1;  }  @Caching(evict = {@CacheEvict(value = CategoryCacheConstants.CATEGORY_DOMAIN,      key = "#category.getCategoryCacheKey()",      beforeInvocation = true)})  public int delete(Category category) {    System.out.println("模擬進行數據庫交互操作......");    System.out.println("Cache became invalid,value:" + CategoryCacheConstants.CATEGORY_DOMAIN        + ",key:" + category.getCategoryCacheKey());    return 0;  }  @Caching(evict = {@CacheEvict(value = CategoryCacheConstants.CATEGORY_DOMAIN,      key = "#category.getCategoryCacheKey()")})  public int update(Category category) {    System.out.println("模擬進行數據庫交互操作......");    System.out.println("Cache updated,value:" + CategoryCacheConstants.CATEGORY_DOMAIN        + ",key:" + category.getCategoryCacheKey()        + ",category:" + category);    return 1;  }  @Cacheable(value = CategoryCacheConstants.CATEGORY_DOMAIN,      key = "#category.getCategoryCacheKey()")  public Category get(Category category) {    System.out.println("模擬進行數據庫交互操作......");    Category result = new Category();    result.setCateId(category.getCateId());    result.setCateName(category.getCateId() + "CateName");    result.setParentId(category.getCateId() - 10);    return result;  }}

CategoryService 通過對 category 對象的數據庫增刪改查,模擬緩存失效和緩存增加的結果。使用非常簡便,把注解加在方法上,則可以達到緩存的生效和失效方案。

深入源碼

源碼分析我們分為幾個方面一步一步解釋其中的實現原理和實現細節。源碼基于 Spring 4.3.7.RELEASE 分析。

發現

SpringCache 在方法上使用注解發揮緩存的作用,緩存的發現是基于 AOP 的 PointCut 和 MethodMatcher 通過在注入的 class 中找到每個方法上的注解,并解析出來。

首先看到 org.springframework.cache.annotation.SpringCacheAnnotationParser 類:

protected Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) { Collection<CacheOperation> ops = null; Collection<Cacheable> cacheables = AnnotatedElementUtils.getAllMergedAnnotations(ae, Cacheable.class); if (!cacheables.isEmpty()) { ops = lazyInit(ops); for (Cacheable cacheable : cacheables) {  ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable)); } } Collection<CacheEvict> evicts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CacheEvict.class); if (!evicts.isEmpty()) { ops = lazyInit(ops); for (CacheEvict evict : evicts) {  ops.add(parseEvictAnnotation(ae, cachingConfig, evict)); } } Collection<CachePut> puts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CachePut.class); if (!puts.isEmpty()) { ops = lazyInit(ops); for (CachePut put : puts) {  ops.add(parsePutAnnotation(ae, cachingConfig, put)); } } 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;}

這個方法會解析 Cacheable、CacheEvict、CachePut 和 Caching 4個注解,找到方法上的這4個注解后,會將注解中的參數解析出來,作為后續注解生效的一個依據。這里舉例說一下 CacheEvict 注解。

CacheEvictOperation parseEvictAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) { CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder(); builder.setName(ae.toString()); builder.setCacheNames(cacheEvict.cacheNames()); builder.setCondition(cacheEvict.condition()); builder.setKey(cacheEvict.key()); builder.setKeyGenerator(cacheEvict.keyGenerator()); builder.setCacheManager(cacheEvict.cacheManager()); builder.setCacheResolver(cacheEvict.cacheResolver()); builder.setCacheWide(cacheEvict.allEntries()); builder.setBeforeInvocation(cacheEvict.beforeInvocation()); defaultConfig.applyDefault(builder); CacheEvictOperation op = builder.build(); validateCacheOperation(ae, op); return op;}

CacheEvict 注解是用于緩存失效。這里代碼會根據 CacheEvict 的配置生產一個 CacheEvictOperation 的類,注解上的 name、key、cacheManager 和 beforeInvocation 等都會傳遞進來。

另外需要將一下 Caching 注解,這個注解通過 parseCachingAnnotation 方法解析參數,會拆分成 Cacheable、CacheEvict、CachePut 注解,也就對應我們緩存中的增加、失效和更新操作。

Collection<CacheOperation> parseCachingAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching) { Collection<CacheOperation> ops = null; Cacheable[] cacheables = caching.cacheable(); if (!ObjectUtils.isEmpty(cacheables)) { ops = lazyInit(ops); for (Cacheable cacheable : cacheables) {  ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable)); } } CacheEvict[] cacheEvicts = caching.evict(); if (!ObjectUtils.isEmpty(cacheEvicts)) { ops = lazyInit(ops); for (CacheEvict cacheEvict : cacheEvicts) {  ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict)); } } CachePut[] cachePuts = caching.put(); if (!ObjectUtils.isEmpty(cachePuts)) { ops = lazyInit(ops); for (CachePut cachePut : cachePuts) {  ops.add(parsePutAnnotation(ae, defaultConfig, cachePut)); } } return ops;}

然后回到 AbstractFallbackCacheOperationSource 類:

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); if (cached != null) { return (cached != NULL_CACHING_ATTRIBUTE ? cached : null); } 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; }}

這里會將解析出來的 CacheOperation 放在當前 Map<Object, Collection<CacheOperation>> attributeCache = new ConcurrentHashMap<Object, Collection<CacheOperation>>(1024); 屬性上,為后續攔截方法時處理緩存做好數據的準備。

注解產生作用

當訪問 categoryService.get(category) 方法時,會走到 CglibAopProxy.intercept() 方法,這也說明緩存注解是基于動態代理實現,通過方法的攔截來動態設置或失效緩存。方法中會通過 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); 來拿到當前調用方法的 Interceptor 鏈。往下走會調用 CacheInterceptor 的 invoke 方法,最終調用 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); } } // 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;}

我們的方法沒有使用同步,走到 processCacheEvicts 方法。

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); } }}

注意這個方法傳入的 beforeInvocation 參數是 true,說明是方法執行前進行的操作,這里是取出 CacheEvictOperation,operation.isBeforeInvocation(),調用下面方法:

private void performCacheEvict(CacheOperationContext context, CacheEvictOperation operation, Object result) { Object key = null; for (Cache cache : context.getCaches()) { if (operation.isCacheWide()) {  logInvalidating(context, operation, null);  doClear(cache); } else {  if (key == null) {  key = context.generateKey(result);  }  logInvalidating(context, operation, key);  doEvict(cache, key); } }}

這里需要注意了,operation 中有個參數 cacheWide,如果使用這個參數并設置為true,則在緩存失效時,會調用 clear 方法進行全部緩存的清理,否則只對當前 key 進行 evict 操作。本文中,doEvict() 最終會調用到 ConcurrentMapCache的evict(Object key) 方法,將 key 緩存失效。

回到 execute 方法,走到 Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class)); 這一步,這里會根據當前方法是否有 CacheableOperation 注解,進行緩存的查詢,如果沒有命中緩存,則會調用方法攔截器 CacheInterceptor 的 proceed 方法,進行原方法的調用,得到緩存 key 對應的 value,然后通過 cachePutRequest.apply(cacheValue) 設置緩存。

public void apply(Object result) { if (this.context.canPutToCache(result)) { for (Cache cache : this.context.getCaches()) {  doPut(cache, this.key, result); } }}

doPut() 方法最終對調用到 ConcurrentMapCache 的 put 方法,完成緩存的設置工作。

最后 execute 方法還有最后一步 processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue); 處理針對執行方法后緩存失效的注解策略。

優缺點

優點

方便快捷高效,可直接嵌入多個現有的 cache 實現,簡寫了很多代碼,可觀性非常強。

缺點

  • 內部調用,非 public 方法上使用注解,會導致緩存無效。由于 SpringCache 是基于 Spring AOP 的動態代理實現,由于代理本身的問題,當同一個類中調用另一個方法,會導致另一個方法的緩存不能使用,這個在編碼上需要注意,避免在同一個類中這樣調用。如果非要這樣做,可以通過再次代理調用,如 ((Category)AopContext.currentProxy()).get(category) 這樣避免緩存無效。
  • 不能支持多級緩存設置,如默認到本地緩存取數據,本地緩存沒有則去遠端緩存取數據,然后遠程緩存取回來數據再存到本地緩存。

擴展知識點

  • 動態代理:JDK、CGLIB代理。
  • SpringAOP、方法攔截器。

Demo

https://github.com/zhuzhenke/common-caches

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩成人久久久| 欧美日韩午夜激情| 97热精品视频官网| 欧美性猛交视频| 日韩在线视频国产| 欧美日韩成人网| 最近2019中文字幕第三页视频| 中文字幕日韩欧美在线| 色哟哟亚洲精品一区二区| 综合激情国产一区| 欧美一区二区三区图| 亚洲一区亚洲二区亚洲三区| 亚洲成人久久一区| 精品国产一区二区三区四区在线观看| 欧美午夜女人视频在线| 色999日韩欧美国产| 久久视频中文字幕| 国产精自产拍久久久久久| 性欧美激情精品| 日本乱人伦a精品| 日本亚洲欧美三级| 国产精品都在这里| 亚洲三级av在线| www.欧美精品| 国产精品久久久久久久av大片| 成人精品在线观看| 国产精品视频免费在线| 92福利视频午夜1000合集在线观看| 91在线精品视频| 欧美国产乱视频| y97精品国产97久久久久久| 海角国产乱辈乱精品视频| 热久久免费视频精品| 欧美电影院免费观看| 日韩av电影手机在线| 亚洲福利在线视频| 中文字幕欧美精品日韩中文字幕| 日本高清视频一区| 亚洲国产精品久久久| 欧美一区二区视频97| 国产视频在线观看一区二区| 欧美精品福利视频| 91热精品视频| 国产精品久久久久99| 粉嫩老牛aⅴ一区二区三区| 亚洲摸下面视频| 精品香蕉一区二区三区| 精品国产乱码久久久久酒店| 欧美激情视频播放| 久久久久这里只有精品| 亚洲高清久久网| 亚洲国产高清高潮精品美女| 国产精品高精视频免费| 福利精品视频在线| 国产中文字幕亚洲| 色偷偷av一区二区三区| 日韩欧美国产成人| 国产亚洲一级高清| 一本色道久久综合亚洲精品小说| 国产免费亚洲高清| 成人免费视频97| 亚洲欧美中文在线视频| 久久影视电视剧免费网站| 日韩va亚洲va欧洲va国产| 一区二区三区四区在线观看视频| 亚洲一区精品电影| 国产一区二区三区18| 久久久久久久久久久人体| 97香蕉久久超级碰碰高清版| 欧美一级淫片播放口| 国产精品美女午夜av| 欧美美女15p| 欧美日韩在线视频一区二区| 亚洲缚视频在线观看| 亚洲欧洲一区二区三区在线观看| 国产精品久久久999| 亚洲自拍另类欧美丝袜| 久久视频精品在线| 日韩电影大全免费观看2023年上| 成人字幕网zmw| 热久久视久久精品18亚洲精品| 欧美国产精品va在线观看| 俺去了亚洲欧美日韩| 日韩中文字幕在线免费观看| 国产在线播放91| 亚洲第一男人天堂| 日韩成人黄色av| 亚洲福利小视频| 久久久久久久久中文字幕| 欧美国产日韩二区| 日本不卡视频在线播放| 91最新在线免费观看| 中文字幕亚洲综合久久筱田步美| 日韩精品免费电影| 日韩黄色高清视频| 国产精品久久久av| 国内伊人久久久久久网站视频| 中文字幕亚洲色图| 播播国产欧美激情| 欧美精品xxx| 91人成网站www| 成人国产亚洲精品a区天堂华泰| 成人免费观看网址| 国产91精品视频在线观看| 亚洲免费av电影| 在线电影av不卡网址| 久久在线免费视频| 欧美成人亚洲成人| 国产精品福利网| 亚洲欧美中文日韩v在线观看| 欧美日韩不卡合集视频| 亚洲成人中文字幕| 国产精品第2页| 中文字幕亚洲字幕| 亚洲免费成人av电影| 清纯唯美亚洲综合| 亚洲精品影视在线观看| 日本精品视频在线| 91精品国产91久久久久久| 亚洲人成电影网站色…| 久久99热精品这里久久精品| 成人精品aaaa网站| 亚洲一区二区福利| 国产精品亚洲片夜色在线| 国产91精品在线播放| 日韩av一区在线观看| 欧美制服第一页| 中文字幕亚洲激情| 青青草成人在线| 亚洲毛茸茸少妇高潮呻吟| 91在线精品视频| 亚洲天堂av在线播放| 91香蕉嫩草影院入口| 奇米影视亚洲狠狠色| 亚洲国产古装精品网站| 奇米成人av国产一区二区三区| 亚洲国产成人爱av在线播放| 亚洲精品之草原avav久久| 一二美女精品欧洲| 亚洲精品成人久久久| 这里只有精品视频| 亚洲最大的成人网| 55夜色66夜色国产精品视频| 国产成人精品视频在线观看| 欧美日韩午夜激情| 亚洲男人av电影| 日本高清不卡的在线| 91色琪琪电影亚洲精品久久| 6080yy精品一区二区三区| 精品亚洲永久免费精品| 日韩欧美精品网站| 亚洲精品国产欧美| 欧美成人精品一区二区三区| 日本精品中文字幕| 国产日产久久高清欧美一区| 日韩精品在线免费观看| 亚洲精品乱码久久久久久金桔影视| 亚洲人成电影网站色xx| 日韩欧美国产激情| 日韩精品电影网| 国产美女久久精品香蕉69| 国产成人在线一区| 欧美最猛性xxxxx(亚洲精品)| 国产日韩在线看片|