电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁技術文章
文章詳情頁

Spring Cache擴展功能實現過程解析

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

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

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

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

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

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

上面兩種各有利弊,并不是說哪一種一定要比另外一種強,根據自己項目的實際情況選擇。

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

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

Spring Cache擴展功能實現過程解析

Srping 4.3提供了一個sync參數。是當緩存失效后,為了避免多個請求打到數據庫,系統做了一個并發控制優化,同時只有一個線程會去數據庫取數據其它線程會被阻塞。

背景

我以Spring Cache +Redis為前提來實現上面兩個需求,其它類型的緩存原理應該是相同的。

本文內容未在生產環境驗證過,也許有不妥的地方,請多多指出。

擴展RedisCacheManagerCustomizedRedisCacheManager

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

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

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

解析配置的value值,分別計算出真正的緩存名稱,失效時間以及緩存刷新的時間 調用構造函數返回緩存對象

@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

主要是實現緩存即將過期時能夠主動觸發緩存更新,核心是下面這個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() { //重新加載數據 logger.info('refresh key:{}',key);CustomizedRedisCache.this.getCacheSupport().refreshCacheByKey(CustomizedRedisCache.super.getName(),key.toString());} }); } } return valueWrapper;}

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

攔截@Cacheable,并記錄執行方法信息

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

CacheSupport

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

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

InvocationRegistry

執行方法注冊接口,能夠在適當的地方主動調用方法執行來完成緩存的更新。

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

CachedInvocation

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

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

這個類主要實現上面定義的緩存刷新接口以及執行方法注冊接口

刷新緩存

獲取cacheManager用來操作緩存:

@Autowiredprivate CacheManager cacheManager;

實現緩存刷新接口方法:

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

反射來調用方法:

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

緩存刷新最后實際執行是這個方法,通過invoke函數獲取到最新的數據,然后通過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);}}}

執行方法信息注冊

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

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

實現執行方法信息接口,構造執行方法對象然后存儲到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方法信息并完成注冊,將使用了緩存的方法的執行信息存儲到Map中,key是緩存容器的名稱,value是不同參數的方法執行實例,核心方法就是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();}

客戶端調用

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

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

代碼

可以從項目中下載。

Spring Cache擴展功能實現過程解析

引用

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

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

標簽: Spring
相關文章:
主站蜘蛛池模板: SDG吸附剂,SDG酸气吸附剂,干式酸性气体吸收剂生产厂家,超过20年生产使用经验。 - 富莱尔环保设备公司(原名天津市武清县环保设备厂) | 齿辊分级破碎机,高低压压球机,立式双动力磨粉机-郑州长城冶金设备有限公司 | 烟气换热器_GGH烟气换热器_空气预热器_高温气气换热器-青岛康景辉 | 药品冷藏箱厂家_低温冰箱_洁净工作台-济南欧莱博电子商务有限公司官网 | 智能案卷柜_卷宗柜_钥匙柜_文件流转柜_装备柜_浙江福源智能科技有限公司 | 披萨石_披萨盘_电器家电隔热绵加工定制_佛山市南海区西樵南方综合保温材料厂 | 污水处理设备维修_污水处理工程改造_机械格栅_过滤设备_气浮设备_刮吸泥机_污泥浓缩罐_污水处理设备_污水处理工程-北京龙泉新禹科技有限公司 | 西门子气候补偿器,锅炉气候补偿器-陕西沃信机电工程有限公司 | PC阳光板-PC耐力板-阳光板雨棚-耐力板雨棚,厂家定制[优尼科板材] | 小小作文网_中小学优秀作文范文大全| 农业四情_农业气象站_田间小型气象站_智慧农业气象站-山东风途物联网 | 沥青车辙成型机-车托式混凝土取芯机-混凝土塑料试模|鑫高仪器 | 太阳能发电系统-太阳能逆变器,控制器-河北沐天太阳能科技首页 | 河北凯普威医疗器材有限公司,高档轮椅系列,推车系列,座厕椅系列,协步椅系列,拐扙系列,卫浴系列 | 烟台金蝶财务软件,烟台网站建设,烟台网络推广 | 复合土工膜厂家|hdpe防渗土工膜|复合防渗土工布|玻璃纤维|双向塑料土工格栅-安徽路建新材料有限公司 | 钢格板_钢格栅_格栅板_钢格栅板 - 安平县鑫拓钢格栅板厂家 | 医养体检包_公卫随访箱_慢病随访包_家签随访包_随访一体机-济南易享医疗科技有限公司 | 上海小程序开发-小程序制作-上海小程序定制开发公司-微信商城小程序-上海咏熠 | 密集架|电动密集架|移动密集架|黑龙江档案密集架-大量现货厂家销售 | 智慧养老_居家养老_社区养老_杰佳通 | 【直乐】河北石家庄脊柱侧弯医院_治疗椎间盘突出哪家医院好_骨科脊柱外科专业医院_治疗抽动症/关节病骨伤权威医院|排行-直乐矫形中医医院 | 阳光1号桔柚_无核沃柑_柑橘新品种枝条苗木批发 - 苧金网 | 双吸泵,双吸泵厂家,OS双吸泵-山东博二泵业有限公司 | 一路商机网-品牌招商加盟优选平台-加盟店排行榜平台 | 精密钢管,冷拔精密无缝钢管,精密钢管厂,精密钢管制造厂家,精密钢管生产厂家,山东精密钢管厂家 | 设定时间记录电子秤-自动累计储存电子秤-昆山巨天仪器设备有限公司 | 耐酸碱胶管_耐腐蚀软管总成_化学品输送软管_漯河利通液压科技耐油耐磨喷砂软管|耐腐蚀化学软管 | 大型多片锯,圆木多片锯,方木多片锯,板材多片锯-祥富机械有限公司 | 原子吸收设备-国产分光光度计-光谱分光光度计-上海光谱仪器有限公司 | 瓶盖扭矩仪(扭力值检测)-百科 | 餐饮小吃技术培训-火锅串串香培训「何小胖培训」_成都点石成金[官网] | 河南凯邦机械制造有限公司| 楼承板-开口楼承板-闭口楼承板-无锡海逵 | 风电变桨伺服驱动器-风电偏航变桨系统-深圳众城卓越科技有限公司 | 杭州中央空调维修_冷却塔/新风机柜/热水器/锅炉除垢清洗_除垢剂_风机盘管_冷凝器清洗-杭州亿诺能源有限公司 | 中式装修设计_室内中式装修_【云臻轩】中式设计机构 | 工业废水处理|污水处理厂|废水治理设备工程技术公司-苏州瑞美迪 今日娱乐圈——影视剧集_八卦娱乐_明星八卦_最新娱乐八卦新闻 | 深圳标识制作公司-标识标牌厂家-深圳广告标识制作-玟璟广告-深圳市玟璟广告有限公司 | 光泽度计_测量显微镜_苏州压力仪_苏州扭力板手维修-苏州日升精密仪器有限公司 | 高铝砖-高铝耐火球-高铝耐火砖生产厂家-价格【荣盛耐材】 |