首页 > 作文

一次排查@CacheEvict注解失效的经历及解决

更新时间:2023-04-04 05:25:37 阅读: 评论:0

目录
排查@cacheevict注解失效下面是我通过源码跟踪排查问题的过程小结一下说说spring全家桶中@cacheevict无效情况举个例子

排查@cacheevict注解失效

我简单看了一下《spring实战》中的demo,然后就应用到业务代码中了,本以为如此简单的事情,竟然在代码提交后的1个周,被同事发现。lectbytaskid()方法查出来的数据总是过时的。

代码如下:

@cacheable("taskparamscache")list<taskparams> lectbytaskid(long taskid);// ...// ...@cacheevict("taskparamscache")int deletebytaskid(long taskid);

想要的效果是当程序调用lectbytaskid()方法时,把结果缓存下来,然后在调用deletebytaskid()方法时,将缓存清空。

经过数据库数据对比之后,把问题排查的方向定位在@cacheevict注解失效了。

下面是我通过源码跟踪排查问题的过程

在韩国的英文怎么写deletebytaskid()方法的调用出打断点,跟进代码到spring生成的代理层。

@override@nullablepublic object intercept(object proxy, method method, object[] args, methodproxy methodproxy) throws throwable {object oldproxy = null;boolean tproxycontext = fal;object target = null;targetsource targetsource = t数据挖掘技术his.advid.gettargetsource();try {if (this.advid.expoproxy) {// make invocation available if necessary.oldproxy = aopcontext.tcurrentproxy(proxy);tproxycontext = true;}// get as late as possible to minimize the time we "own" the target, in ca it comes from a pool...target = targetsource.gettarget();class<?> targetclass = (target != null ? target.getclass() : null);list<object> chain = this.advid.getinterceptorsanddynamicinterceptionadvice(method, targetclass);object retval;// check whether we only have one invokerinterceptor: that is,// no real advice, but just reflective invocation of the target.if (chain.impty() && modifier.ispublic(method.getmodifiers())) {// we can skip creating a methodinvocation: just invoke the target directly.// note that the final invoker must be an invokerinterceptor, so we know// it does nothing but a reflective operation on the target, and no hot// swapping or fancy proxying.object[] argstou = aopproxyutils.adaptargumentsifnecessary(method, args);retval = methodproxy.invoke(target, argstou);}el {// we need to create a method invocation...retval = new cglibmethodinvocation(proxy, target, method, args, targetclass, chain, methodproxy).proceed();}retval = processreturntype(proxy, target, method, retval);return retval;}finally {if (target != null && !targetsource.isstatic()) {targetsource.releatarget(target);}if (tproxycontext) {// restore old proxy.aopcontext.tcurrentproxy(oldproxy);}}}

通过getinterceptorsanddynamicinterceptionadvice获取到当前方法的拦截器,里面包含了cacheineterceptor,说明注解被spring检测到了。

进入cglibmethodinvocation(proxy, target, method, args, targetclass, chain, methodproxy).proceed()方法内部

org.springframework.aop.framework.reflectivemethodinvocation#proceed

@override@nullablepublic object proceed() throws throwable {//we start with an index of -1 and increment early.if (this.currentinterceptorindex == this.interceptorsanddynamicmethodmatchers.size() - 1) {return invokejoinpoint();}object interceptororinterceptionadvice =this.interceptorsanddynamicmethodmatchers.get(++this.currentinterceptorindex);if (interceptororinterceptionadvice instanceof interceptoranddynamicmethodmatcher) {// evaluate dynamic method matcher here: static part will already have// been evaluated and found to match.interceptoranddynamicmethodmatcher dm =(interceptoranddynamicmethodmatcher) interceptororinterceptionadvice;if (dm.methodmatcher.matches(this.method, this.targetclass, this.arguments)) {return dm.interceptor.invoke(this);}el {// dynamic matching failed.// skip this interceptor and invoke the next in the chain.return proceed();}}el {// it's an interceptor, so we just invoke it: the pointcut will have// been evaluated statically before this object was constructed.return ((methodinterceptor) interceptororinterceptionadvice).invoke(this);}}

this.interceptorsanddynamicmethodmatchers.get(++this.currentinterceptorindex)方法取第一个拦截器,正是我们要关注的cacheineterceptor,然后调用((methodinterceptor) interceptororinterceptionadvice).invoke(this)方法,继续跟进

org.springframework.cache.interceptor.cacheinterceptor#invoke

@override@nullablepublic object invoke(final methodinvocation invocation) throws throwable {method method = invocation.getmethod();cacheoperationinvoker aopallianceinvoker = () -> {try {return invocation.proceed();}catch (throwable ex) {throw new cacheope动词做主语rationinvoker.throwablewrapper(ex);}};try {return execute(aopallianceinvoker, invocation.getthis(), method, invocation.getarguments());}catch (cacheoperationinvoker.throwablewrapper th) {throw th.getoriginal();}}

进入execute方法

protected object execute(cacheoperationinvoker invoker, object target, method method, object[] args) {// check whether aspect is enabled (to cope with cas where the aj is pulled in automatically)if (this.initialized) {class<?> targetclass = gettargetclass(target);cacheoperationsource cacheoperationsource = getcacheoperationsource();if (cacheoperationsource != null) {collection<cacheoperation> operations = cacheoperationsource.getcacheoperations(method, targetclass);if (!collectionutils.impty(operations)) {return execute(invoker, method,new cacheoperationcontexts(operations, method, args, target, targetclass));}}}return invoker.invoke();}

cacheoperationsource记录系统中所有使用了缓存的方法,cacheoperationsource.getcacheoperations(method, targetclass)能获取deletebytaskid()方法缓存元数据,然后执行execute()方法

@nullableprivate object execute(final cacheoperationinvoker invoker, method method, cacheoperationcontexts contexts) {// special handling of synchronized invocationif (contexts.issynchronized()) {cacheoperationcontext context = contexts.get(cacheable奇异博士彩蛋operation.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, () -> 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.getcau();}}el {// no caching required, only call the underlying methodreturn invokeoperation(invoker);}}// process any early evictionsprocesscacheevicts(contexts.get(cacheevictoperation.class), true,cacheoperationexpressionevaluator.no_result);// check if we have a cached item matching the conditionscache.valuewrapper cachehit = findcacheditem(contexts.get(cacheableoperation.class));// collect puts from any @cacheable miss, if no cached item is foundlist<cacheputrequest> cacheputrequests = new linkedlist<>();if (cachehit == null) {collectputrequests(contexts.get(cacheableoperation.class),cacheoperationexpressionevaluator.no_result, cacheputrequests);}object cachevalue;object returnvalue;if (cachehit != null && cacheputrequests.impty() && !hascacheput(contexts)) {// if there are no put requests, just u the cache hitcachevalue = cachehit.get();returnvalue = wrapcachevalue(method, cachevalue);}el {// invoke the method if we don't have a cache hitreturnvalue = invokeoperation(invoker);cachevalue = unwrapreturnvalue(returnvalue);}// collect any explicit @cacheputscollectputrequests(contexts.get(cacheputoperation.class), cachevalue, cacheputrequests);// process any collected put requests, either from @cacheput or a @cacheable missfor (cacheputrequest cacheputrequest : cacheputrequests) {cacheputrequest.apply(cachevalue);}// process any late evictionsprocesscacheevicts(contexts.get(cacheevictoperation.class), fal, cachevalue);return returnvalue;}

这里大致过程是:

先执行beforinvokeevict —- 执行数据库delete操作 — 执行cacheput操作 —- 执行afterinvokeevict

我们的注解是方法调用后再使缓存失效,直接所以有效的操作应在倒数第2行

private void performcacheevict(cacheoperationcontext context, cacheevictoperation operation, @nullable object result) {object key = null;for (cache cache : context.getcaches()) {if (operation.iscachewide()) {loginvalidating(context, operation, null);doclear(cache);}el {if (key == null) {key = generatekey(context, result);}loginvalidating(context, operation, key);doevict(cache, key);}}}

这里通过context.getcaches()获取到name为taskparamscache的缓存

然后generatekey生成key,注意这里,发现生成的key是com.xxx.xxx.atomic.impl.xxxxdeletebytaskid982,但是缓存中的key却是com.xxx.xxx.atomic.impl.xxxxlectbytaskid982,下面调用的doevict(cache, key)方法不再跟进了,就是从cache中移除key对应值。明显这里key对应不上的,这也是导致@cacheevict没有生效的原因。

小结一下

我还是太一件小事给我的启示大意了,当时看了注解@cacheevict的对key的注释:

大意就是如果没有指定key,那就会使用方法所有参数生成一个key,明显com.xxx.xxx.atomic.impl.xxxxlectbytaskid982是方法名 + 参数,可是你没说把方法名还加上了啊,说好的只用参数呢,哈哈,这个bug是我使用不当引出的,很多人不会犯这种低级错误。

解决办法就是使用spel明确定义key

@cacheable(value = "taskparamscache", key = "#taskid")list<taskparams> lectbytaskid(long taskid);// ...// ...@cacheevict(value = "taskparamscache", key = "#taskid")int deletebytaskid(long taskid);

说说spring全家桶中@cacheevict无效情况

@cacheevict(value =“test”, allentries = true)

1、使用@cacheevict注解的方法必须是controller层直接调用,rvice里间接调用不生效。

2、原因是因为key值跟你查询方法的key值不统一,所以导致缓存并没有清除

3、把@cacheevict的方法和@cache的方法放到一个java文件中写,他俩在两个java文件的话,会导致@cacheevict失效。

4、返回值必须设置为void

@cacheevict annotation

it is important to note that void methods can be ud with @cacheevict

5、@cacheevict必须作用在走代理的方法上

在使用spring @cacheevict注解的时候,要注意,如果类a的方法f1()被标注了 @cacheevict注解,那么当类a的其他方法,例如:f2(),去直接调用f1()的时候, @cacheevict是不起作用的,原因是 @cacheevict是基于spring aop代理类,f2()属于内部方法,直接调用f1()时,是不走代理的。

举个例子

不生效:

@overridepublic void saveentity(menu menu) {  try {    mapper.inrt(menu);    //cacheable 不生效    this.test();  }catch(exception e){    e.printstacktrace();  }}@cacheevict(value = "test" , allentries = true)public void test() {}

正确使用:

@override@cacheevict(value = "test" , allentries = true)public void saveentity(menu menu) {  try {    mapper.inrt(menu);  }catch(exception e){    e.printstacktrace();  }}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持www.887551.com。

本文发布于:2023-04-04 05:25:36,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/68defb219129c8a55f3549290c00fd47.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:一次排查@CacheEvict注解失效的经历及解决.doc

本文 PDF 下载地址:一次排查@CacheEvict注解失效的经历及解决.pdf

下一篇:返回列表
标签:方法   注解   缓存   代码
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图