cacheable注解(cacheable注解不生效)

技术@CacheEvict注解失效的经历及解决方法是什么@CacheEvict注解失效的经历及解决方法是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望

@缓存驱逐注解失效的经历及解决方法是什么,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

排查@CacheEvict注解失效

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

代码如下:

@可缓存(“任务参数缓存”)

listasksparamselectbytaskid(LongtaskId);

//.

//.

@ cachedrout(' Taskparamscache ')

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

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

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

在deleteByTaskId()方法的调用出打断点,跟进代码到春天生成的代理层。

@覆盖

@可空

公共对象概念(对象代理,方法方法,对象[]参数,methodproxymethod proxy)throw throw表{

ObjectoldProxy=null

booleansetProxyContext=false

Objecttarget=null

targetsource targetsource=this。建议。gettargetsource();

尝试{

如果(这个。建议。exposeproxy){ 0

//Makeinvocationavailable必要条件.

oldProxy=aopcontext。setcurrentproxy(代理);

setProxyContext=真

}

//getslateaspossibletomize时间我们"拥有"目标,在incaseitcomesfromapool中.

目标=目标源。gettarget();

上课?targetClass=(目标!=null?目标。GetClass()(: null);

ListObjectchain=this。建议。getinterceptors和dynamicinterexceptionadvice(方法,目标类);

ObjectretVal

//检查我们是否只有一个invokerinterceptor :这是,

//noraladvice,但是但是只是目标的反射性参与.

if(链。isempty()修饰符。ispublic(方法。getmodifiers())){ 0

//Wecanskipcreatinga

nbsp;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[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// 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.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}

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

@CacheEvict注解失效的经历及解决方法是什么

进入CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed()方法内部

org.springframework.aop.framework.ReflectiveMethodInvocation#proceed

@Override
	@Nullable
	public 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);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			// 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
	@Nullable
	public Object invoke(final MethodInvocation invocation) throws Throwable {
		Method method = invocation.getMethod();
		CacheOperationInvoker aopAllianceInvoker = () -> {
			try {
				return invocation.proceed();
			}
			catch (Throwable ex) {
				throw new CacheOperationInvoker.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 cases 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.isEmpty(operations)) {
					return execute(invoker, method,
							new CacheOperationContexts(operations, method, args, target, targetClass));
				}
			}
		}
		return invoker.invoke();
	}

cacheOperationSource记录系统中所有使用了缓存的方法,cacheOperationSource.getCacheOperations(method, targetClass)能获取deleteByTaskId()方法缓存元数据,然后执行execute()方法

@Nullable
	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, () -> 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);
			}
		}
		// Process any early evictions
		processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
				CacheOperationExpressionEvaluator.NO_RESULT);
		// Check if we have a cached item matching the conditions
		Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
		// Collect puts from any @Cacheable miss, if no cached item is found
		List<CachePutRequest> cachePutRequests = new LinkedList<>();
		if (cacheHit == null) {
			collectPutRequests(contexts.get(CacheableOperation.class),
					CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
		}
		Object cacheValue;
		Object returnValue;
		if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
			// If there are no put requests, just use the cache hit
			cacheValue = cacheHit.get();
			returnValue = wrapCacheValue(method, cacheValue);
		}
		else {
			// Invoke the method if we don't have a cache hit
			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;
	}

这里大致过程是:

先执行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);
			}
			else {
				if (key == null) {
					key = generateKey(context, result);
				}
				logInvalidating(context, operation, key);
				doEvict(cache, key);
			}
		}
	}

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

@CacheEvict注解失效的经历及解决方法是什么

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

小结一下

我还是太大意了,当时看了注解@CacheEvict的对key的注释:

@CacheEvict注解失效的经历及解决方法是什么

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

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

@Cacheable(value = "taskParamsCache", key = "#taskId")
List<TaskParams> selectByTaskId(Long taskId);
// ...
// ...
@CacheEvict(value = "taskParamsCache", key = "#taskId")
int deleteByTaskId(Long taskId);

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

@CacheEvict(value =“test”, allEntries = true)

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

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

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

4、返回值必须设置为void

@CacheEvict annotation

It is important to note that void methods can be used with @CacheEvict

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

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

举个例子

不生效:

@Override
public void saveEntity(Menu menu) {
  try {
    mapper.insert(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.insert(menu);
  }catch(Exception e){
    e.printStackTrace();
  }
}

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注行业资讯频道,感谢您对的支持。

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/157909.html

(0)

相关推荐

  • JavaScript中ECMAScript是什么

    技术JavaScript中ECMAScript是什么这篇文章主要介绍了JavaScript中ECMAScript是什么,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起

    攻略 2021年10月30日
  • 怎样可以,和平精英怎么才能变厉害

    技术怎样可以,和平精英怎么才能变厉害大家好怎样可以!这里是爱分享的零助攻!很高兴回答您的问题!和平精英怎么样变厉害?可以说需要具备的条件太多了。第一:能力问题能力又分先天和后天,有的人天生就是玩游戏的好手,接受能力和适应

    生活 2021年10月21日
  • 怎么解决consul重启引发问题

    技术怎么解决consul重启引发问题这篇文章将为大家详细讲解有关怎么解决consul重启引发问题,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。2019/07/12 13:

    攻略 2021年10月20日
  • nagiosQL怎么安装

    技术nagiosQL怎么安装这篇文章主要介绍了nagiosQL怎么安装,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。安装nagiosql1.yum -y

    攻略 2021年10月21日
  • 85的因数有哪些,85和100的最大公因数是什么

    技术85的因数有哪些,85和100的最大公因数是什么85和100的最大公因数是585的因数有哪些。85=517。
    100=5x5x4。
    拓展资料最大公因数,也称最大公约数、最大公因子,指两个或多个整数共有约数中最大的一个

    生活 2021年10月24日
  • vue中的生命周期和钩子函数是什么

    技术vue中的生命周期和钩子函数是什么这篇文章主要讲解了“vue中的生命周期和钩子函数是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vue中的生命周期和钩子函数是什

    攻略 2021年12月4日