Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the experience and solution of @ CacheEvict annotation failure

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

Many novices are not very clear about the experience and solution of @ CacheEvict annotation failure. In order to help you solve this problem, the following editor will explain it in detail. People with this need can come and learn. I hope you can gain something.

Troubleshoot @ CacheEvict annotation failure

I took a brief look at the demo in "Spring practice" and then applied it to the business code. What I thought was such a simple thing was discovered by my colleagues a week after the code was submitted. The data found by the selectByTaskId () method is always out of date.

The code is as follows:

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

The desired effect is that the result is cached when the program calls the selectByTaskId () method, and then the cache is cleared when the deleteByTaskId () method is called.

After comparing the database data, the troubleshooting direction is that the @ CacheEvict annotation is invalid.

Here is my process of troubleshooting problems through source code tracking

At the break point of the call to the deleteByTaskId () method, follow the code to the proxy layer generated by spring.

@ Override @ Nullable public Object intercept (Object proxy, Method method, Object [] args, MethodProxy methodProxy) throws Throwable {Object oldProxy = null; boolean setProxyContext = 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 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);}

The interceptor of the current method is obtained through getInterceptorsAndDynamicInterceptionAdvice, which contains CacheIneterceptor, indicating that the annotation has been detected by spring.

Enter inside the CglibMethodInvocation (proxy, target, method, args, targetClass, chain, methodProxy) .proceed () method

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

The this.interceptorsAndDynamicMethodMatchers.get (+ + this.currentInterceptorIndex) method takes the first interceptor, which is the CacheIneterceptor we want to focus on, and then calls the ((MethodInterceptor) interceptorOrInterceptionAdvice) .invoke (this) method to continue with the follow-up

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

Enter the execute method

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 operations = cacheOperationSource.getCacheOperations (method, targetClass) If (! CollectionUtils.isEmpty (operations)) {return execute (invoker, method, new CacheOperationContexts (operations, method, args, target, targetClass)) } return invoker.invoke ();}

CacheOperationSource records all the caching methods in the system. CacheOperationSource.getCacheOperations (method, targetClass) can get the deleteByTaskId () method cache metadata, and then execute the execute () method.

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

The general process here is:

First execute beforInvokeEvict-execute database delete operation-execute CachePut operation-execute afterInvokeEvict

Our note is that the cache is invalidated after the method call, so the effective operation should be on the penultimate line.

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

Here, the cache whose name is taskParamsCache is obtained through context.getCaches ()

Then generateKey generates key. Notice here that the generated key is com.xxx.xxx.atomic.impl.xxxxdeleteByTaskId982, but the key in the cache is com.xxx.xxx.atomic.impl.xxxxselectByTaskId982. The doEvict (cache, key) method called below no longer follows, just removes the key corresponding value from the cache. Obviously, the key does not match here, which is the reason why @ CacheEvict does not take effect.

Make a brief summary

I was still too careless. At that time, I read the note on key in the note @ CacheEvict:

The general idea is that if key is not specified, then all the parameters of the method will be used to generate a key. Obviously, com.xxx.xxx.atomic.impl.xxxxselectByTaskId982 is the method name + parameter, but you didn't say to add the method name, ah, as agreed, only parameters are used. Haha, this bug is caused by my improper use, and many people will not make such a low-level mistake.

The solution is to use SpEL to clearly define key

@ Cacheable (value = "taskParamsCache", key = "# taskId") List selectByTaskId (Long taskId); / /... /... @ CacheEvict (value = "taskParamsCache", key = "# taskId") int deleteByTaskId (Long taskId); talk about @ CacheEvict invalidity in spring buckets @ CacheEvict (value = "test", allEntries = true)

1. The method using @ CacheEvict annotation must be called directly by the controller layer, and the indirect call in service does not take effect.

2. The reason is that the cache is not cleared because the key value is not consistent with the key value of your query method.

3. Put the @ CacheEvict method and the @ Cache method into one java file, and if they are in two java files, it will cause @ CacheEvict to become invalid.

4. The return value must be set to void

@ CacheEvict annotation

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

5. @ CacheEvict must act on the method of acting as an agent

When using the Spring @ CacheEvict annotation, note that if the method F1 () of class An is annotated with @ CacheEvict, then @ CacheEvict does not work when other methods of class A, such as f2 (), call F1 () directly, because @ CacheEvict is based on the Spring AOP proxy class, f2 () belongs to the internal method, and does not use the proxy when calling F1 () directly.

For instance

Does not take effect:

@ Overridepublic void saveEntity (Menu menu) {try {mapper.insert (menu); / / Cacheable does not take effect this.test ();} catch (Exception e) {e.printStackTrace ();}} @ CacheEvict (value = "test", allEntries = true) public void test () {}

Use it correctly:

@ Override@CacheEvict (value = "test", allEntries = true) public void saveEntity (Menu menu) {try {mapper.insert (menu);} catch (Exception e) {e.printStackTrace ();}} is it helpful for you to read the above content? If you want to know more about the relevant knowledge or read more related articles, please follow the industry information channel, thank you for your support.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report