日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術(shù)文章
文章詳情頁

Spring Cache擴(kuò)展功能實(shí)現(xiàn)過程解析

瀏覽:172日期:2023-09-19 16:18:50

兩個需求緩存失效時間支持在方法的注解上指定

Spring Cache默認(rèn)是不支持在@Cacheable上添加過期時間的,可以在配置緩存容器時統(tǒng)一指定:

@Beanpublic CacheManager cacheManager( @SuppressWarnings('rawtypes') RedisTemplate redisTemplate) { CustomizedRedisCacheManager cacheManager= new CustomizedRedisCacheManager(redisTemplate); cacheManager.setDefaultExpiration(60); Map<String,Long> expiresMap=new HashMap<>(); expiresMap.put('Product',5L); cacheManager.setExpires(expiresMap); return cacheManager;}

想這樣配置過期時間,焦點(diǎn)在value的格式上Product#5#2,詳情下面會詳細(xì)說明。

@Cacheable(value = {'Product#5#2'},key ='#id')

上面兩種各有利弊,并不是說哪一種一定要比另外一種強(qiáng),根據(jù)自己項(xiàng)目的實(shí)際情況選擇。

在緩存即將過期時主動刷新緩存

一般緩存失效后,會有一些請求會打到后端的數(shù)據(jù)庫上,這段時間的訪問性能肯定是比有緩存的情況要差很多。所以期望在緩存即將過期的某一時間點(diǎn)后臺主動去更新緩存以確保前端請求的緩存命中率,示意圖如下:

Spring Cache擴(kuò)展功能實(shí)現(xiàn)過程解析

Srping 4.3提供了一個sync參數(shù)。是當(dāng)緩存失效后,為了避免多個請求打到數(shù)據(jù)庫,系統(tǒng)做了一個并發(fā)控制優(yōu)化,同時只有一個線程會去數(shù)據(jù)庫取數(shù)據(jù)其它線程會被阻塞。

背景

我以Spring Cache +Redis為前提來實(shí)現(xiàn)上面兩個需求,其它類型的緩存原理應(yīng)該是相同的。

本文內(nèi)容未在生產(chǎn)環(huán)境驗(yàn)證過,也許有不妥的地方,請多多指出。

擴(kuò)展RedisCacheManagerCustomizedRedisCacheManager

繼承自RedisCacheManager,定義兩個輔助性的屬性:

/** * 緩存參數(shù)的分隔符 * 數(shù)組元素0=緩存的名稱 * 數(shù)組元素1=緩存過期時間TTL * 數(shù)組元素2=緩存在多少秒開始主動失效來強(qiáng)制刷新 */ private String separator = '#'; /** * 緩存主動在失效前強(qiáng)制刷新緩存的時間 * 單位:秒 */ private long preloadSecondTime=0;

注解配置失效時間簡單的方法就是在容器名稱上動動手腳,通過解析特定格式的名稱來變向?qū)崿F(xiàn)失效時間的獲取。比如第一個#后面的5可以定義為失效時間,第二個#后面的2是刷新緩存的時間,只需要重寫getCache:

解析配置的value值,分別計(jì)算出真正的緩存名稱,失效時間以及緩存刷新的時間 調(diào)用構(gòu)造函數(shù)返回緩存對象

@Overridepublic Cache getCache(String name) { String[] cacheParams=name.split(this.getSeparator()); String cacheName = cacheParams[0]; if(StringUtils.isBlank(cacheName)){ return null; } Long expirationSecondTime = this.computeExpiration(cacheName); if(cacheParams.length>1) { expirationSecondTime=Long.parseLong(cacheParams[1]); this.setDefaultExpiration(expirationSecondTime); } if(cacheParams.length>2) { this.setPreloadSecondTime(Long.parseLong(cacheParams[2])); } Cache cache = super.getCache(cacheName); if(null==cache){ return cache; } logger.info('expirationSecondTime:'+expirationSecondTime); CustomizedRedisCache redisCache= new CustomizedRedisCache( cacheName, (this.isUsePrefix() ? this.getCachePrefix().prefix(cacheName) : null), this.getRedisOperations(), expirationSecondTime, preloadSecondTime); return redisCache;}

CustomizedRedisCache

主要是實(shí)現(xiàn)緩存即將過期時能夠主動觸發(fā)緩存更新,核心是下面這個get方法。在獲取到緩存后再次取緩存剩余的時間,如果時間小余我們配置的刷新時間就手動刷新緩存。為了不影響get的性能,啟用后臺線程去完成緩存的刷新。

public ValueWrapper get(Object key) { ValueWrapper valueWrapper= super.get(key); if(null!=valueWrapper){ Long ttl= this.redisOperations.getExpire(key); if(null!=ttl&& ttl<=this.preloadSecondTime){ logger.info('key:{} ttl:{} preloadSecondTime:{}',key,ttl,preloadSecondTime); ThreadTaskHelper.run(new Runnable() {@Overridepublic void run() { //重新加載數(shù)據(jù) logger.info('refresh key:{}',key);CustomizedRedisCache.this.getCacheSupport().refreshCacheByKey(CustomizedRedisCache.super.getName(),key.toString());} }); } } return valueWrapper;}

ThreadTaskHelper是個幫助類,但需要考慮重復(fù)請求問題,及相同的數(shù)據(jù)在并發(fā)過程中只允許刷新一次,這塊還沒有完善就不貼代碼了。

攔截@Cacheable,并記錄執(zhí)行方法信息

上面提到的緩存獲取時,會根據(jù)配置的刷新時間來判斷是否需要刷新數(shù)據(jù),當(dāng)符合條件時會觸發(fā)數(shù)據(jù)刷新。但它需要知道執(zhí)行什么方法以及更新哪些數(shù)據(jù),所以就有了下面這些類。

CacheSupport

刷新緩存接口,可刷新整個容器的緩存也可以只刷新指定鍵的緩存。

public interface CacheSupport {/** * 刷新容器中所有值 * @param cacheName */void refreshCache(String cacheName);/** * 按容器以及指定鍵更新緩存 * @param cacheName * @param cacheKey */void refreshCacheByKey(String cacheName,String cacheKey);}

InvocationRegistry

執(zhí)行方法注冊接口,能夠在適當(dāng)?shù)牡胤街鲃诱{(diào)用方法執(zhí)行來完成緩存的更新。

public interface InvocationRegistry {void registerInvocation(Object invokedBean, Method invokedMethod, Object[] invocationArguments, Set<String> cacheNames);}

CachedInvocation

執(zhí)行方法信息類,這個比較簡單,就是滿足方法執(zhí)行的所有信息即可。

public final class CachedInvocation { private Object key; private final Object targetBean; private final Method targetMethod; private Object[] arguments; public CachedInvocation(Object key, Object targetBean, Method targetMethod, Object[] arguments) { this.key = key; this.targetBean = targetBean; this.targetMethod = targetMethod; if (arguments != null && arguments.length != 0) { this.arguments = Arrays.copyOf(arguments, arguments.length); } }}

CacheSupportImpl

這個類主要實(shí)現(xiàn)上面定義的緩存刷新接口以及執(zhí)行方法注冊接口

刷新緩存

獲取cacheManager用來操作緩存:

@Autowiredprivate CacheManager cacheManager;

實(shí)現(xiàn)緩存刷新接口方法:

@Overridepublic void refreshCache(String cacheName) {this.refreshCacheByKey(cacheName,null);}@Overridepublic void refreshCacheByKey(String cacheName, String cacheKey) {if (cacheToInvocationsMap.get(cacheName) != null) {for (final CachedInvocation invocation : cacheToInvocationsMap.get(cacheName)) {if(!StringUtils.isBlank(cacheKey)&&invocation.getKey().toString().equals(cacheKey)) {refreshCache(invocation, cacheName);}}}}

反射來調(diào)用方法:

private Object invoke(CachedInvocation invocation)throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {final MethodInvoker invoker = new MethodInvoker();invoker.setTargetObject(invocation.getTargetBean());invoker.setArguments(invocation.getArguments());invoker.setTargetMethod(invocation.getTargetMethod().getName());invoker.prepare();return invoker.invoke();}

緩存刷新最后實(shí)際執(zhí)行是這個方法,通過invoke函數(shù)獲取到最新的數(shù)據(jù),然后通過cacheManager來完成緩存的更新操作。

private void refreshCache(CachedInvocation invocation, String cacheName) {boolean invocationSuccess;Object computed = null;try {computed = invoke(invocation);invocationSuccess = true;} catch (Exception ex) {invocationSuccess = false;}if (invocationSuccess) {if (cacheToInvocationsMap.get(cacheName) != null) {cacheManager.getCache(cacheName).put(invocation.getKey(), computed);}}}

執(zhí)行方法信息注冊

定義一個Map用來存儲執(zhí)行方法的信息:

private Map<String, Set<CachedInvocation>> cacheToInvocationsMap;

實(shí)現(xiàn)執(zhí)行方法信息接口,構(gòu)造執(zhí)行方法對象然后存儲到Map中。

@Overridepublic void registerInvocation(Object targetBean, Method targetMethod, Object[] arguments, Set<String> annotatedCacheNames) {StringBuilder sb = new StringBuilder();for (Object obj : arguments) {sb.append(obj.toString());}Object key = sb.toString();final CachedInvocation invocation = new CachedInvocation(key, targetBean, targetMethod, arguments);for (final String cacheName : annotatedCacheNames) {String[] cacheParams=cacheName.split('#');String realCacheName = cacheParams[0];if(!cacheToInvocationsMap.containsKey(realCacheName)) {this.initialize();}cacheToInvocationsMap.get(realCacheName).add(invocation);}}

CachingAnnotationsAspect

攔截@Cacheable方法信息并完成注冊,將使用了緩存的方法的執(zhí)行信息存儲到Map中,key是緩存容器的名稱,value是不同參數(shù)的方法執(zhí)行實(shí)例,核心方法就是registerInvocation。

@Around('pointcut()')public Object registerInvocation(ProceedingJoinPoint joinPoint) throws Throwable{Method method = this.getSpecificmethod(joinPoint);List<Cacheable> annotations=this.getMethodAnnotations(method,Cacheable.class);Set<String> cacheSet = new HashSet<String>();for (Cacheable cacheables : annotations) {cacheSet.addAll(Arrays.asList(cacheables.value()));}cacheRefreshSupport.registerInvocation(joinPoint.getTarget(), method, joinPoint.getArgs(), cacheSet);return joinPoint.proceed();}

客戶端調(diào)用

指定5秒后過期,并且在緩存存活3秒后如果請求命中,會在后臺啟動線程重新從數(shù)據(jù)庫中獲取數(shù)據(jù)來完成緩存的更新。理論上前端不會存在緩存不命中的情況,當(dāng)然如果正好最后兩秒沒有請求那也會出現(xiàn)緩存失效的情況。

@Cacheable(value = {'Product#5#2'},key ='#id')public Product getById(Long id) { //...}

代碼

可以從項(xiàng)目中下載。

Spring Cache擴(kuò)展功能實(shí)現(xiàn)過程解析

引用

刷新緩存的思路取自于這個開源項(xiàng)目。https://github.com/yantrashala/spring-cache-self-refresh

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持好吧啦網(wǎng)。

標(biāo)簽: Spring
相關(guān)文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
色在线视频观看| 欧美一级二级视频| 国产一区二区三区黄网站| 麻豆91在线播放| 老鸭窝一区二区久久精品| 麻豆国产一区| 日本视频在线一区| 国产伦精品一区二区三区在线播放 | 亚洲综合精品| 日韩影院免费视频| 三级久久三级久久久| 久久国内精品| 开心激情综合| 日韩av在线播放网址| 日韩在线观看| 国产色综合网| 日韩影片在线观看| 久久xxx视频| 天堂av在线| 136国产福利精品导航网址| 99国产精品视频免费观看一公开 | 国产精品二区影院| 精品深夜福利视频| 成人自拍av| 老鸭窝亚洲一区二区三区| 日韩精品视频网| 国产精品综合| 欧美xxxx中国| 蜜桃tv一区二区三区| 亚洲色图综合| 久久99影视| 久久国产主播| 最新国产精品| 精品国产91| 蜜桃国内精品久久久久软件9| 欧美一区=区| 国产欧美日本| 免费高潮视频95在线观看网站| 老司机久久99久久精品播放免费| 日韩精彩视频在线观看| 成人在线视频区| 久久中文亚洲字幕| 综合国产视频| 91亚洲人成网污www| 天堂va蜜桃一区二区三区| 久久av偷拍| 2023国产精品久久久精品双| 欧美日韩1区| 久久久国产精品一区二区中文| 日本 国产 欧美色综合| 麻豆视频一区| 欧美日韩国产高清电影| 国产伦精品一区二区三区千人斩 | 日韩成人午夜精品| 午夜精品久久久久久久久久蜜桃| 蜜桃久久久久久| 国产成人精品一区二区三区在线| 亚洲精品97| 国产精品日本一区二区不卡视频| 亚洲午夜在线| 国产精品欧美大片| 亚洲欧美日韩国产一区二区| 欧美激情网址| 99在线观看免费视频精品观看| 欧美国产不卡| 只有精品亚洲| 久久久精品午夜少妇| 国产欧美亚洲一区| 国产真实久久| 麻豆精品蜜桃视频网站| 中日韩男男gay无套| 精品久久福利| 日本成人在线不卡视频| 不卡中文一二三区| 国产在线日韩精品| 日韩精品亚洲aⅴ在线影院| 久久久久久久久丰满| 国产欧美日韩一级| 久热精品在线| 激情婷婷欧美| 91视频一区| 国产精品黄网站| 日本在线成人| 亚洲夜间福利| 精品欠久久久中文字幕加勒比| 美国三级日本三级久久99| 久久久久久黄| 都市激情国产精品| 国产精品亚洲片在线播放| 蜜桃一区二区三区在线观看| 欧美日韩亚洲在线观看| 国产欧洲在线| 你懂的国产精品永久在线| 日韩中文av| 天堂av在线一区| 欧美~级网站不卡| 成人午夜精品| 日本不良网站在线观看| 老色鬼精品视频在线观看播放| 日本成人手机在线| 综合亚洲自拍| 免费在线成人网| 日韩视频二区| 国产在线|日韩| av日韩中文| 高清av一区| 成人国产精品| av资源新版天堂在线| 国精品产品一区| 精品午夜视频| 国产精品精品国产一区二区| 久久精品毛片| 国精品产品一区| 免费观看亚洲天堂| 欧美亚洲人成在线| 国产亚洲一区| 欧美日一区二区三区在线观看国产免 | 国产美女视频一区二区| 久久国内精品视频| 欧美亚洲三区| 国产精品一区三区在线观看| 欧美久久久网站| 国产极品一区| 国产精品日本一区二区不卡视频 | 婷婷精品视频| 欧美午夜精品一区二区三区电影| 婷婷激情一区| 免费国产自久久久久三四区久久| 亚洲午夜视频| 香蕉成人久久| 日韩在线网址| 国产丝袜一区| 精品免费av| 欧美日韩视频网站| 亚洲一级特黄| 99成人在线| 在线观看一区| 国产日韩欧美三区| 老司机免费视频一区二区三区| 高清在线一区| 91精品婷婷色在线观看| 五月天久久久| 伊人久久大香伊蕉在人线观看热v| 亚欧成人精品| 精品视频在线观看网站| 日韩在线不卡| 蜜桃av一区| 欧美日韩精品一区二区三区在线观看| 国产精品一国产精品k频道56| 精品一区二区三区亚洲 | 99热国内精品| 蜜臀久久99精品久久久久久9| 偷拍亚洲精品| 美女av一区| 极品裸体白嫩激情啪啪国产精品| 欧美在线综合| 国产乱码精品| 四虎4545www国产精品| 黄色亚洲在线| 日韩激情网站| 成午夜精品一区二区三区软件| 国产字幕视频一区二区| 在线精品一区二区| 国产私拍福利精品视频二区| 亚洲伊人av| 日韩专区欧美专区| 国产精品资源| 激情久久久久久| 日韩国产一二三区| 成人综合一区| 日韩在线卡一卡二| 精品国产亚洲一区二区三区在线| 精品一区在线| 国产欧美一区二区色老头| 日韩国产欧美| 久久性天堂网| 福利一区二区免费视频| 中文精品视频| 国产精品草草| 在线 亚洲欧美在线综合一区| 国产美女精品视频免费播放软件| 欧美日韩中文一区二区| 国产欧美日韩一区二区三区在线| 久久亚洲国产| 国产精品香蕉| 国产精品美女| 水蜜桃久久夜色精品一区| 在线视频亚洲欧美中文| 神马久久午夜| 日韩在线成人| 久久精品国产www456c0m| 7777精品| 久久视频精品| 国产精品综合色区在线观看| 午夜国产一区二区| 久久精品理论片| 亚洲精品一级二级三级| 久久婷婷久久| 美女国产精品久久久| 老司机精品久久|