最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一次排查@CacheEvict注解失效的經(jīng)歷及解決

 更新時(shí)間:2021年12月24日 16:50:48   作者:小飯大人  
這篇文章主要介紹了一次排查@CacheEvict注解失效的經(jīng)歷及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

排查@CacheEvict注解失效

我簡(jiǎn)單看了一下《Spring實(shí)戰(zhàn)》中的demo,然后就應(yīng)用到業(yè)務(wù)代碼中了,本以為如此簡(jiǎn)單的事情,竟然在代碼提交后的1個(gè)周,被同事發(fā)現(xiàn)。selectByTaskId()方法查出來(lái)的數(shù)據(jù)總是過(guò)時(shí)的。

代碼如下:

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

想要的效果是當(dāng)程序調(diào)用selectByTaskId()方法時(shí),把結(jié)果緩存下來(lái),然后在調(diào)用deleteByTaskId()方法時(shí),將緩存清空。

經(jīng)過(guò)數(shù)據(jù)庫(kù)數(shù)據(jù)對(duì)比之后,把問(wèn)題排查的方向定位在@CacheEvict注解失效了。

下面是我通過(guò)源碼跟蹤排查問(wèn)題的過(guò)程

在deleteByTaskId()方法的調(diào)用出打斷點(diǎn),跟進(jìn)代碼到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);
				}
			}
		}

通過(guò)getInterceptorsAndDynamicInterceptionAdvice獲取到當(dāng)前方法的攔截器,里面包含了CacheIneterceptor,說(shuō)明注解被spring檢測(cè)到了。

在這里插入圖片描述

進(jìn)入CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed()方法內(nèi)部

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)方法取第一個(gè)攔截器,正是我們要關(guān)注的CacheIneterceptor,然后調(diào)用((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this)方法,繼續(xù)跟進(jìn)

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();
		}
	}

進(jìn)入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記錄系統(tǒng)中所有使用了緩存的方法,cacheOperationSource.getCacheOperations(method, targetClass)能獲取deleteByTaskId()方法緩存元數(shù)據(jù),然后執(zhí)行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;
	}

這里大致過(guò)程是:

先執(zhí)行beforInvokeEvict ---- 執(zhí)行數(shù)據(jù)庫(kù)delete操作 --- 執(zhí)行CachePut操作 ---- 執(zhí)行afterInvokeEvict

我們的注解是方法調(diào)用后再使緩存失效,直接所以有效的操作應(yīng)在倒數(shù)第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);
			}
		}
	}

這里通過(guò)context.getCaches()獲取到name為taskParamsCache的緩存

在這里插入圖片描述

然后generateKey生成key,注意這里,發(fā)現(xiàn)生成的key是com.xxx.xxx.atomic.impl.xxxxdeleteByTaskId982,但是緩存中的key卻是com.xxx.xxx.atomic.impl.xxxxselectByTaskId982,下面調(diào)用的doEvict(cache, key)方法不再跟進(jìn)了,就是從cache中移除key對(duì)應(yīng)值。明顯這里key對(duì)應(yīng)不上的,這也是導(dǎo)致@CacheEvict沒(méi)有生效的原因。

小結(jié)一下

我還是太大意了,當(dāng)時(shí)看了注解@CacheEvict的對(duì)key的注釋?zhuān)?/p>

在這里插入圖片描述

大意就是如果沒(méi)有指定key,那就會(huì)使用方法所有參數(shù)生成一個(gè)key,明顯com.xxx.xxx.atomic.impl.xxxxselectByTaskId982是方法名 + 參數(shù),可是你沒(méi)說(shuō)把方法名還加上了啊,說(shuō)好的只用參數(shù)呢,哈哈,這個(gè)bug是我使用不當(dāng)引出的,很多人不會(huì)犯這種低級(jí)錯(cuò)誤。

解決辦法就是使用SpEL明確定義key

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

說(shuō)說(shuō)spring全家桶中@CacheEvict無(wú)效情況

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

1、使用@CacheEvict注解的方法必須是controller層直接調(diào)用,service里間接調(diào)用不生效。

2、原因是因?yàn)閗ey值跟你查詢(xún)方法的key值不統(tǒng)一,所以導(dǎo)致緩存并沒(méi)有清除

3、把@CacheEvict的方法和@Cache的方法放到一個(gè)java文件中寫(xiě),他倆在兩個(gè)java文件的話,會(huì)導(dǎo)致@CacheEvict失效。

4、返回值必須設(shè)置為void

@CacheEvict annotation

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

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

在使用Spring @CacheEvict注解的時(shí)候,要注意,如果類(lèi)A的方法f1()被標(biāo)注了 @CacheEvict注解,那么當(dāng)類(lèi)A的其他方法,例如:f2(),去直接調(diào)用f1()的時(shí)候, @CacheEvict是不起作用的,原因是 @CacheEvict是基于Spring AOP代理類(lèi),f2()屬于內(nèi)部方法,直接調(diào)用f1()時(shí),是不走代理的。

舉個(gè)例子

不生效:

@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();
  }
}

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java報(bào)錯(cuò):找不到或無(wú)法加載主類(lèi)的解決辦法

    Java報(bào)錯(cuò):找不到或無(wú)法加載主類(lèi)的解決辦法

    在Java中當(dāng)您嘗試運(yùn)行一個(gè)類(lèi)作為主類(lèi)時(shí),如果系統(tǒng)找不到該類(lèi)或者無(wú)法加載該類(lèi),就會(huì)出現(xiàn)"找不到或無(wú)法加載主類(lèi)"的錯(cuò)誤,這篇文章主要給大家介紹了關(guān)于Java報(bào)錯(cuò):找不到或無(wú)法加載主類(lèi)的解決辦法,需要的朋友可以參考下
    2024-12-12
  • 一文帶你搞懂java如何實(shí)現(xiàn)網(wǎng)絡(luò)NIO高并發(fā)編程

    一文帶你搞懂java如何實(shí)現(xiàn)網(wǎng)絡(luò)NIO高并發(fā)編程

    NIO是?Java?在?JDK?1.4?中引入的一套新的?I/O?API,旨在解決傳統(tǒng)?I/O高并發(fā)場(chǎng)景下的性能和擴(kuò)展性不足的問(wèn)題,下面就跟隨小編一起深入了解下NIO高并發(fā)編程吧
    2024-12-12
  • Java中的大數(shù)類(lèi)簡(jiǎn)單實(shí)現(xiàn)

    Java中的大數(shù)類(lèi)簡(jiǎn)單實(shí)現(xiàn)

    這篇文章主要介紹了Java中的大數(shù)類(lèi)簡(jiǎn)單實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2017-03-03
  • SpringBoot集成itext導(dǎo)出PDF的過(guò)程

    SpringBoot集成itext導(dǎo)出PDF的過(guò)程

    本文介紹了如何在Spring Boot中集成iText庫(kù)導(dǎo)出PDF文件,并解決中文亂碼問(wèn)題,步驟包括添加依賴(lài)、準(zhǔn)備字體、打開(kāi)系統(tǒng)字體目錄選擇字體、在控制器中新增方法、創(chuàng)建并測(cè)試UserPdfExportService類(lèi),以及添加請(qǐng)求頭,感興趣的朋友一起看看吧
    2024-11-11
  • Java實(shí)現(xiàn)的百度語(yǔ)音識(shí)別功能示例

    Java實(shí)現(xiàn)的百度語(yǔ)音識(shí)別功能示例

    這篇文章主要介紹了Java實(shí)現(xiàn)的百度語(yǔ)音識(shí)別功能,較為簡(jiǎn)明扼要的分析了Java調(diào)用百度語(yǔ)音接口相關(guān)操作步驟,并給出了具體的語(yǔ)音識(shí)別用法代碼示例,需要的朋友可以參考下
    2018-08-08
  • HashMap每次擴(kuò)容為什么是2倍

    HashMap每次擴(kuò)容為什么是2倍

    當(dāng)HashMap在初始化沒(méi)有指定容量的情況下,首次添加元素時(shí),數(shù)組的容量為16;當(dāng)超出閾值,數(shù)組容量為擴(kuò)容為之前的2倍,為什么HashMap每次擴(kuò)容都是之前的2倍?下面就介紹一下
    2024-11-11
  • Hadoop2.8.1完全分布式環(huán)境搭建過(guò)程

    Hadoop2.8.1完全分布式環(huán)境搭建過(guò)程

    本文搭建了一個(gè)由三節(jié)點(diǎn)(master、slave1、slave2)構(gòu)成的Hadoop完全分布式集群(區(qū)別單節(jié)點(diǎn)偽分布式集群),并通過(guò)Hadoop分布式計(jì)算的一個(gè)示例測(cè)試集群的正確性。對(duì)hadoop分布式環(huán)境搭建過(guò)程感興趣的朋友跟隨小編一起看看吧
    2019-06-06
  • IDEA?Reformat?Code?格式化代碼(詳解)

    IDEA?Reformat?Code?格式化代碼(詳解)

    平時(shí)使用Ctrl+Alt+L可以格式化代碼,idea幫你整理空格,換行等,讓代碼看起來(lái)更整潔,今天通過(guò)本文給大家分享IDEA?Reformat?Code?格式化?的過(guò)程,感興趣的朋友一起看看吧
    2023-11-11
  • Java中的final關(guān)鍵字使用方式

    Java中的final關(guān)鍵字使用方式

    這篇文章主要介紹了Java中的final關(guān)鍵字使用方式,final 關(guān)鍵字用于修飾不可改變內(nèi)容,更多相關(guān)梳理總結(jié),需要的小伙伴可以參考下面文章內(nèi)容
    2022-06-06
  • Java中的輸出格式化問(wèn)題小結(jié)

    Java中的輸出格式化問(wèn)題小結(jié)

    在Java中,System.out.printf方法用于格式化輸出,格式化字符串`%.6f`表示浮點(diǎn)數(shù)保留6位小數(shù),其他格式化選項(xiàng)包括`%d`(整數(shù))、`%s`(字符串)和`%e`(科學(xué)計(jì)數(shù)法),示例代碼展示了如何使用這些格式化選項(xiàng),感興趣的朋友一起看看吧
    2025-02-02

最新評(píng)論

资溪县| 宣汉县| 兴仁县| 剑川县| 会同县| 手游| 嘉禾县| 呼图壁县| 夏邑县| 沧源| 苍南县| 蒙山县| 隆子县| 兰西县| 长垣县| 丰都县| 遵义县| 克什克腾旗| 宣化县| 土默特左旗| 章丘市| 崇左市| 醴陵市| 吉水县| 栾川县| 安义县| 舒兰市| 房产| 巴马| 卓资县| 离岛区| 田东县| 将乐县| 山东| 永寿县| 万州区| 梅州市| 博罗县| 苏州市| 西藏| 德庆县|