关于Spring Cache 缓存拦截器( CacheInterceptor)
程序员文章站
2022-03-06 13:29:39
目录spring cache 缓存拦截器( cacheinterceptor)spring cache常用的三种缓存操作具体整个流程是这样的cacheinterceptor.java定义cacheab...
spring cache 缓存拦截器( cacheinterceptor)
打开spring cache的核心缓存拦截器cacheinterceptor,可以看到具体实现:
public class cacheinterceptor extends cacheaspectsupport implements methodinterceptor, serializable { @override public object invoke(final methodinvocation invocation) throws throwable { method method = invocation.getmethod(); cacheoperationinvoker aopallianceinvoker = new cacheoperationinvoker() { @override public object invoke() { try { return invocation.proceed(); } catch (throwable ex) { throw new throwablewrapper(ex); } } }; try { return execute(aopallianceinvoker, invocation.getthis(), method, invocation.getarguments()); } catch (cacheoperationinvoker.throwablewrapper th) { throw th.getoriginal(); } } }
cacheinterceptor默认实现了spring aop的methodinterceptor接口,methodinterceptor的功能是做方法拦截。拦截的方法都会调用invoke方法,在invoke方法里面主要缓存逻辑是在execute方法里面,该方法是继承了父类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); //获取执行方法上所有的缓存操作集合。如果有缓存操作则执行到execute(...),如果没有就执行invoker.invoke()直接调用执行方法了 collection<cacheoperation> operations = getcacheoperationsource().getcacheoperations(method, targetclass); if (!collectionutils.isempty(operations)) { return execute(invoker, method, new cacheoperationcontexts(operations, method, args, target, targetclass)); } } return invoker.invoke(); }
集合collection operations中存放了所有的缓存操作cacheputoperation、cacheableoperation、cacheevictoperation
spring cache常用的三种缓存操作
-
@cacheput
:执行方法后,将方法返回结果存放到缓存中。不管有没有缓存过,执行方法都会执行,并缓存返回结果(unless可以否决进行缓存)。(当然,这里说的缓存都要满足condition条件) -
@cacheable
:如果没有缓存过,获取执行方法的返回结果;如果缓存过,则直接从缓存中获取,不再执行方法。 -
@cacheevict
:如果设置了beforeintercepte则在方法执行前进行缓存删除操作,如果没有,则在执行方法调用完后进行缓存删除操作。
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); } } // 处理beforeintercepte=true的缓存删除操作 processcacheevicts(contexts.get(cacheevictoperation.class), true, cacheoperationexpressionevaluator.no_result); // 从缓存中查找,是否有匹配@cacheable的缓存数据 cache.valuewrapper cachehit = findcacheditem(contexts.get(cacheableoperation.class)); // 如果@cacheable没有被缓存,那么就需要将数据缓存起来,这里将@cacheable操作收集成cacheputrequest集合,以便后续做@cacheput缓存数据存放。 list<cacheputrequest> cacheputrequests = new linkedlist<cacheputrequest>(); if (cachehit == null) { collectputrequests(contexts.get(cacheableoperation.class), cacheoperationexpressionevaluator.no_result, cacheputrequests); } object cachevalue; object returnvalue; //如果没有@cacheput操作,就使用@cacheable获取的结果(可能也没有@cableable,所以result可能为空)。 if (cachehit != null && cacheputrequests.isempty() && !hascacheput(contexts)) { //如果没有@cacheput操作,并且cachehit不为空,说明命中缓存了,直接返回缓存结果 cachevalue = cachehit.get(); returnvalue = wrapcachevalue(method, cachevalue); } else { // 否则执行具体方法内容,返回缓存的结果 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; } //根据key从缓存中查找,返回的结果是valuewrapper,它是返回结果的包装器 private cache.valuewrapper findcacheditem(collection<cacheoperationcontext> contexts) { object result = cacheoperationexpressionevaluator.no_result; for (cacheoperationcontext context : contexts) { if (isconditionpassing(context, result)) { object key = generatekey(context, result); cache.valuewrapper cached = findincaches(context, key); if (cached != null) { return cached; } else { if (logger.istraceenabled()) { logger.trace("no cache entry for key '" + key + "' in cache(s) " + context.getcachenames()); } } } } return null; } private cache.valuewrapper findincaches(cacheoperationcontext context, object key) { for (cache cache : context.getcaches()) { cache.valuewrapper wrapper = doget(cache, key); if (wrapper != null) { if (logger.istraceenabled()) { logger.trace("cache entry for key '" + key + "' found in cache '" + cache.getname() + "'"); } return wrapper; } } return null; }
具体整个流程是这样的
cacheinterceptor.java
项目中基本上都需要使用到cache的功能, 但是spring提供的cacheable并不能很好的满足我们的需求, 所以这里自己借助spring思想完成自己的业务逻辑.
定义cacheable注解
@target({elementtype.method, elementtype.type}) @retention(retentionpolicy.runtime) @inherited @documented public @interface cacheable { rediskey value(); string key(); }
定义rediskey.java
public enum rediskeyenum { test_cache("test:", 24, timeunit.hours, "test"); /** * 缓存key的前缀 */ private string keyprefix; /** * 过期时间 */ private long timeout; /** * 过期时间单位 */ private timeunit timeunit; /** * 描述 */ private string desc; private static final string redis_key_defualt_separator = ":"; rediskey(string keyprefix, long timeout, timeunit timeunit, string desc){ this.keyprefix = keyprefix; this.timeout = timeout; this.timeunit = timeunit; this.desc = desc; } public long gettimeout() { return timeout; } public timeunit gettimeunit() { return timeunit; } public string getdesc() { return desc; } /** * 获取完整的缓存key * @param keys * @return */ public string getkey(string... keys) { if(keys == null || keys.length <= 0){ return this.keyprefix; } string rediskey = keyprefix; for (int i = 0, length = keys.length; i < length; i++) { string key = keys[i]; rediskey += key; if (i < length - 1) { rediskey += redis_key_defualt_separator; } } return rediskey; } }
cache.java
public interface cache<k, v> { /** * 返回缓存名称 * @return */ string getname(); /** * 添加一个缓存实例 * * @param key * @param value */ v put(k key, v value); /** * 添加一个可过期的缓存实例 * @param key * @param value * @param expire * @param timeunit * @return */ v put(k key, v value, long expire, timeunit timeunit); /** * 返回缓存数据 * * @param key * @return */ v get(k key); /** * 删除一个缓存实例, 并返回缓存数据 * * @param key * @return */ void remove(k key); /** * 获取所有的缓存key * @return */ set<k> keys(); /** * 获取所有的缓存key * @return */ set<k> keys(k pattern); /** * 获取所有的缓存数据 * @return */ collection<v> values(); /** * 清空所有缓存 */ void clear(); }
rediscache.java
public class rediscache<k, v> implements cache<k, v> { public static final string default_cache_name = rediscache.class.getname() + "_cache_name"; private redistemplate<k, v> redistemplate; private valueoperations<k, v> valueoperations; public rediscache(redistemplate redistemplate) { this.redistemplate = redistemplate; this.valueoperations = redistemplate.opsforvalue(); datatype datatype = redistemplate.type("a"); } @override public string getname() { return default_cache_name; } @override public v put(k key, v value) { valueoperations.set(key, value); return value; } @override public v put(k key, v value, long expire, timeunit timeunit) { valueoperations.set(key, value, expire, timeunit); return value; } @override public v get(k key) { return valueoperations.get(key); } @override public void remove(k key) { // v value = valueoperations.get(key); redistemplate.delete(key); } @override public set<k> keys() { return null; } @override public set<k> keys(k pattern) { return redistemplate.keys(pattern); } @override public collection<v> values() { return null; } @override public void clear() { } }
cachemanager.java
public interface cachemanager { /** * 获取缓存 * @return */ cache getcache(string name); /** * 获取所有的缓存名称 */ collection<string> getcachenames(); }
abstractcachemanager.java
public abstract class abstractcachemanager implements cachemanager, initializingbean, disposablebean { private static final logger logger = loggerfactory.getlogger(abstractcachemanager.class); private final map<string, cache> cachemap = new concurrenthashmap<>(16); private volatile set<string> cachenames = collections.emptyset(); private static final string default_cache_name_suffix = "_cache_name"; @override public void afterpropertiesset() throws exception { initlalizingcache(); } private void initlalizingcache(){ collection<? extends cache> caches = loadcaches(); synchronized (this.cachemap) { this.cachenames = collections.emptyset(); this.cachemap.clear(); set<string> cachenames = new linkedhashset<string>(caches.size()); for (cache cache : caches) { string name = cache.getname(); if(stringutils.isempty(name)){ name = cache.getclass().getname() + default_cache_name_suffix; } this.cachemap.put(name, cache); cachenames.add(name); } this.cachenames = collections.unmodifiableset(cachenames); } } @override public cache getcache(string name) { cache cache = cachemap.get(name); if(cache != null){ return cache; } return null; } protected abstract collection<? extends cache> loadcaches(); @override public collection<string> getcachenames() { return this.cachenames; } @override public void destroy() throws exception { cachemap.clear(); } }
rediscachemanager.java
public class rediscachemanager extends abstractcachemanager { private redistemplate redistemplate; public rediscachemanager(redistemplate redistemplate) { this.redistemplate = redistemplate; } @override protected collection<? extends cache> loadcaches() { collection<cache<string, object>> caches = new arraylist<>(); rediscache<string, object> rediscache = new rediscache<>(redistemplate); caches.add(rediscache); return caches; } }
实现cacheinterceptor.java
/** * 缓存数据过滤器, 缓存到redis数据中的数据是serviceresult.getdatemap()数据 * 使用: 在service方法上添加com.chinaredstar.urms.annotations.cacheable注解, 并指定rediskeyeunm和cache key, cache key支持spel表达式 * 以下情况不缓存数据: * 1: 返回状态为fasle时, 不缓存数据 * 2: 返回datamap为空时, 不缓存数据 * 3: 返回数据结构不是servicereslut实例时, 不缓存数据 * * 当缓存问题时, 不影响正常业务, 但所有的请求都会打到db上, 对db有很大的冲击 */ public class cacheinterceptor implements methodinterceptor { private static final logger logger = loggerfactory.getlogger(cacheinterceptor.class); private static final parameternamediscoverer parameternamediscoverer = new defaultparameternamediscoverer(); private cachemanager cachemanager; public void setcachemanager(cachemanager cachemanager) { this.cachemanager = cachemanager; } @override public object invoke(methodinvocation methodinvocation) throws throwable { method method = methodinvocation.getmethod(); object[] args = methodinvocation.getarguments(); cacheable cacheable = method.getannotation(cacheable.class); if (cacheable == null) { return methodinvocation.proceed(); } string key = parsecachekey(method, args, cacheable.key()); logger.info(">>>>>>>> -- 获取缓存key : {}", key); if(stringutils.isempty(key)){ return methodinvocation.proceed(); } rediskey rediskey = cacheable.value(); cache cache = cachemanager.getcache(rediscache.default_cache_name); object value = null; try{ value = cache.get(rediskey.getkey(key)); } catch (exception e){ logger.info(">>>>>>>> -- 从缓存中获取数据异常 : {}", exceptionutil.exceptionstacktrace(e)); } if (value != null) { logger.info(">>>>>>>> -- 从缓存中获取数据 : {}", jsonutil.tojson(value)); return serviceresult.newinstance(true, value); } value = methodinvocation.proceed(); logger.info(">>>>>>>> -- 从接口中获取数据 : {}", jsonutil.tojson(value)); if ( value != null && value instanceof serviceresult ) { serviceresult result = (serviceresult) value; if(!result.issuccess() || result.getdatamap() == null){ return value; } try{ cache.put(rediskey.getkey(key), result.getdatamap(), rediskey.gettimeout(), rediskey.gettimeunit()); } catch (exception e){ logger.info(">>>>>>>> -- 将数据放入缓存异常 : {}", exceptionutil.exceptionstacktrace(e)); } } return value; } /** * 使用spel解析缓存key * @param method * @param args * @param expressionstring * @return */ private string parsecachekey(method method, object[] args, string expressionstring) { string[] parameternames = parameternamediscoverer.getparameternames(method); evaluationcontext context = new standardevaluationcontext(); if (parameternames != null && parameternames.length > 0 && args != null && args.length > 0 && args.length == parameternames.length ) { for (int i = 0, length = parameternames.length; i < length; i++) { context.setvariable(parameternames[i], args[i]); } } expressionparser parser = new spelexpressionparser(); expression expression = parser.parseexpression(expressionstring); return (string) expression.getvalue(context); } }
配置spring.xml
<bean id="rediscachemanager" class="com.package.cache.rediscachemanager"> <constructor-arg ref="cacheredistemplate" /> </bean> <bean id="cacheinterceptor" class="com.package.interceptor.cacheinterceptor" p:cachemanager-ref="rediscachemanager"/> <!-- 方法拦截器 methodinterceptor --> <aop:config proxy-target-class="true"> <aop:pointcut id="cacheinterceptorpointcut" expression="execution(* com.package..*(..)) and @annotation(com.package.annotations.cacheable)"/> <aop:advisor advice-ref="cacheinterceptor" pointcut-ref="cacheinterceptorpointcut" order="2" /> </aop:config>
测试使用
@cacheable(value = rediskey.test_cache, key = "#code + ':' + #user.id") public serviceresult<string> test(string code, user user){ return new serviceresult("success"); }
说明
cacheable其中的参数key拼接的规则支持spring spel表达式。其规则和spring cacheable使用方法一致。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。
上一篇: PHP编程风格规范分享_php基础
下一篇: IIS 短文件/文件夹漏洞修复方法
推荐阅读
-
利用spring的拦截器自定义缓存的实现实例代码
-
关于Java Spring三级缓存和循环依赖的深入理解
-
spring boot+spring cache实现两级缓存(redis+caffeine)
-
spring缓存cache的使用详解
-
Spring Boot Cache Redis缓存
-
Spring Cache+Redis缓存数据的实现示例
-
使用Spring Cache设置缓存条件操作
-
关于Spring Cache 缓存拦截器( CacheInterceptor)
-
Spring Boot如何利用拦截器加缓存完成接口防刷操作
-
spring boot redis 缓存(cache)集成 - qikegu.com