我简单看了一下《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);
@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 条评论) |