中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

@CacheEvict注解失效的經歷及解決方法是什么

發布時間:2021-12-24 21:15:56 來源:億速云 閱讀:462 作者:柒染 欄目:開發技術

@CacheEvict注解失效的經歷及解決方法是什么,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

排查@CacheEvict注解失效

我簡單看了一下《Spring實戰》中的demo,然后就應用到業務代碼中了,本以為如此簡單的事情,竟然在代碼提交后的1個周,被同事發現。selectByTaskId()方法查出來的數據總是過時的。

代碼如下:

@Cacheable("taskParamsCache")
List<TaskParams> selectByTaskId(Long taskId);
// ...
// ...
@CacheEvict("taskParamsCache")
int deleteByTaskId(Long taskId);

想要的效果是當程序調用selectByTaskId()方法時,把結果緩存下來,然后在調用deleteByTaskId()方法時,將緩存清空。

經過數據庫數據對比之后,把問題排查的方向定位在@CacheEvict注解失效了。

下面是我通過源碼跟蹤排查問題的過程

在deleteByTaskId()方法的調用出打斷點,跟進代碼到spring生成的代理層。

@Override
		@Nullable
		public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
			Object oldProxy = null;
			boolean setProxyContext = false;
			Object target = null;
			TargetSource targetSource = this.advised.getTargetSource();
			try {
				if (this.advised.exposeProxy) {
					// Make invocation available if necessary.
					oldProxy = AopContext.setCurrentProxy(proxy);
					setProxyContext = true;
				}
				// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
				target = targetSource.getTarget();
				Class<?> targetClass = (target != null ? target.getClass() : null);
				List<Object> chain = this.advised.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.isEmpty() && 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[] 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();
  }
}

看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

阿克苏市| 顺平县| 邯郸市| 乌苏市| 措美县| 彩票| 罗城| 屯门区| 克什克腾旗| 隆回县| 甘泉县| 阳江市| 凤台县| 阜新市| 腾冲县| 隆安县| 东乌| 张掖市| 沁阳市| 龙井市| 靖安县| 磐石市| 扬中市| 河北省| 长寿区| 苗栗县| 肇州县| 广元市| 改则县| 武乡县| 南康市| 垦利县| 永春县| 金华市| 通河县| 吉水县| 玉田县| 太谷县| 保亭| 攀枝花市| 册亨县|