一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 深入理解Spring Cache框架

深入理解Spring Cache框架

2021-06-15 10:59ImportNew Java教程

今天給大家分析一下 Spring 框架本身對這些緩存具體實現的支持和融合。使用 Spring Cache 將大大的減少我們的Spring項目中緩存使用的復雜度,提高代碼可讀性。本文將從以下幾個方面來認識Spring Cache框架。感興趣的小伙伴們可以參考

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

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

背景

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

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

用法

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

注意點:

1、開啟 enablecaching 注解,默認沒有開啟 cache。

2、配置 cachemanager。

?
1
2
3
4
5
6
@bean
@qualifier("concurrentmapcachemanager")
@primary
concurrentmapcachemanager concurrentmapcachemanager() {
  return new concurrentmapcachemanager();
}

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

現在拿 categoryservice 實現來分析。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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 對象的數據庫增刪改查,模擬緩存失效和緩存增加的結果。使用非常簡便,把注解加在方法上,則可以達到緩存的生效和失效方案。

深入源碼

源碼分析我們分為幾個方面一步一步解釋其中的實現原理和實現細節(jié)。源碼基于 spring 4.3.7.release 分析。

發(fā)現

springcache 在方法上使用注解發(fā)揮緩存的作用,緩存的發(fā)現是基于 aop 的 pointcut 和 methodmatcher 通過在注入的 class 中找到每個方法上的注解,并解析出來。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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個注解后,會將注解中的參數解析出來,作為后續(xù)注解生效的一個依據。這里舉例說一下 cacheevict 注解。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 注解,也就對應我們緩存中的增加、失效和更新操作。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 類:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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); 屬性上,為后續(xù)攔截方法時處理緩存做好數據的準備。

注解產生作用

當訪問 categoryservice.get(category) 方法時,會走到 cglibaopproxy.intercept() 方法,這也說明緩存注解是基于動態(tài)代理實現,通過方法的攔截來動態(tài)設置或失效緩存。方法中會通過 list<object> chain = this.advised.getinterceptorsanddynamicinterceptionadvice(method, targetclass); 來拿到當前調用方法的 interceptor 鏈。往下走會調用 cacheinterceptor 的 invoke 方法,最終調用 execute 方法,我們重點分析這個方法的實現。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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 方法。

?
1
2
3
4
5
6
7
8
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,說明是方法執(zhí)行前進行的操作,這里是取出 cacheevictoperation,operation.isbeforeinvocation(),調用下面方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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) 設置緩存。

?
1
2
3
4
5
6
7
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); 處理針對執(zhí)行方法后緩存失效的注解策略。

優(yōu)缺點

優(yōu)點

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

缺點

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

擴展知識點

  • 動態(tài)代理:jdk、cglib代理。
  • springaop、方法攔截器。

demo

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

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:http://www.importnew.com/30640.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 操久| 国产精品www | 亚洲成人一区在线 | h玉足嫩脚嗯啊白丝 | 国产精品永久免费自在线观看 | 成年极品漫画在线观看 | 亚洲精品久久久打桩机 | 99久久免费国产特黄 | 99热这里只精品99re66 | 啊啊啊好大在线观看 | 久久久亚洲国产精品主播 | 国产亚洲综合久久 | 精品国产一区二区三区久 | 丝袜捆绑调教视频免费区 | 性xxxxxxx18老师 | 91果冻制片厂天美传媒 | 亚洲激情在线视频 | 97porm自拍视频区原创 | 女子监狱第二季未删减在线看 | 51国产午夜精品免费视频 | 精品视频在线免费播放 | 亚洲视频在线观看不卡 | 亚洲国产精品福利片在线观看 | 国内精品久久久久久中文字幕 | 息与子中文字幕完整在线 | 白发在线视频播放观看免费 | 每天都要睡男人(nph) | 91精品国产高清久久久久久91 | 日韩精品特黄毛片免费看 | bl超h 高h 污肉快穿np | 国产精品合集久久久久青苹果 | 四虎影视入口 | 99re5精品视频在线观看 | 性欧美sexvideo另类 | 免费网站看v片在线成人国产系列 | 欧美在线一级视频 | 网红思瑞一区二区三区 | 精品久久久久久久国产潘金莲 | 久久精品观看影院2828 | 国产精品一区二区国产 | 操大姨子逼 |