This article is first published in the public number [look at the code to go to work], welcome to the crowd, the first time to get the latest article.
directory
Spring Cache error
What went wrong?
The Spring Cache principle explains why private methods cannot be cached
Explain from Spring AOP why private methods cannot be cached
Class internal method calls do not support caching
conclusion
Hi, I’m Tin, and this is my 12th original article
Spring Cache error
The background is like this, a colleague developed a function module code, probably query a downstream content interface, query data and forward to the end – side interface. The flow of this function module is very large, and the content data volume of the downstream content interface is also limited (the number of content ids is limited, with an order of magnitude of several W). Colleagues also realized the need to add local cache to the downstream content interface in their own services. The method is exactly the same as the picture below:
When this code is published online, when the side access portal is open, we backend services start to frantically alert, all are content interface is not heavy, response timeout alarm.
Then we went to the call chain first and found that the content interface QPS had soared to 10W!
Why is that? We have already added the interface cache, according to the expectation of the cache, many requests should be sent to the cache, and should not check the downstream content interface. Maybe a lot of people think so, but wrong is wrong.
Errors like the one above are “low-level” and, if used in this way, would be a disaster for slightly less robust downstream systems, and if so, performance will not be much better this year.
What went wrong?
The above code uses spring cache and has the following error:
- 1. Cache private methods
- Class internal method calls plus caching
These problems are deadly, and many of our friends who use caching don’t even know that they can’t use it. Now I will analyze the problems one by one.
Explain why from the Spring Cache principlePrivate methods cannot be cached
Spring Cache is annotated and implemented with Spring AOP. Open the source package and navigate to our @cacheable annotation location:
Cache implementations in the context of org. Springframework. The cache under the package. My Spring version is 5.3.14, and the other versions are pretty much the same.
The matches method checks whether a class or method has cache-specific cache annotations. We all the way to go in, cas. GetCacheOperations (method, targetClass)), the AbstractFallbackCacheOperationSource getCacheOperations method of a class:
AttributeCache is a ConcurrentHashmap that simply caches the computeCacheOperations(Method, targetClass) result so that it doesn’t need to be recalculated next time.
ComputeCacheOperations method, real resolutionOn the wayThe place to cache annotations is in the findCacheOperations method:
All the way to go in, when we see SpringCacheAnnotationParser parseCacheAnnotations method of a class, went to see the spring of the cache associated annotations parsing, and wrap annotations for CacheOperation class
If you look at this,Spring wraps cache annotations on classes and methods into a Collection and determines whether CacheOperation is a pointcut on the AOP aspect.However, what I want to focus on here is that Spring Cache has already judged that it does not support caching annotations on non-public methods. Where is the logic? Careful can be found:
There is obviously no support for non-public methods, even protected methods, let alone private!
It is also easy to find method interceptors after looking at the cache pointcut code:
We follow through with the execute method, which ends up reading or updating the cache in the Execute method of the CacheAspectSupport class.
If we introduce a three-party cache, such as Caffeine, then the underlying store is Caffeine.Cache.
If you want to know how to use the three-way caching tool, check out my other article:
Everyone says Spring Cache! Use it!
Spring cache by
AbstractFallbackCacheOperation# computeCacheOperations explicitly to note the cache does not support the public methods.
Explain why from Spring AOP principlesPrivate methods cannot be cached
Spring Cache does not support caching annotations for non-public methods. If you just look at the spring cache source logic, without this limitation, you can still “work”.
To explain this, start with the principles of Spring AOP.
Let’s change the position of the BookService cache annotation so that the method follows the caching logic:
Start our spring container, breakpoint to our business code bookService. FindByBookNameWithSpringCache (bookName) :
See, the BookService reference is a proxy class, which means spring Cache borrows AOP’s capabilities.
The question is, why cglib proxies?
In our common sense, Spring AOP uses Java dynamic proxies by default, followed by cglib proxies. It can also be confirmed from the spring official website documentation:
Em…… I didn’t use an interface, so I used the Cglib proxy. That’s only half the explanation. (Because even if you change the interface implementation, it still does not work as you want, or cglib proxy, interested friends to try)
Let’s talk about why we are running cglib proxies all the time.
This is spring Boot. We have an @SpringBootApplication annotation on our bootstrap class, which is a set of composite annotations, and we follow the internal definition of this annotation to @EnableAutoConfiguration, Find @ Import (AutoConfigurationImportSelector. Class)
AutoConfigurationImportSelector class process method:
There is a lot of autowiring information in this, as defined by AopAutoConfiguration (this class is defined in spring-boot, not spring-context) :
The main task of the AopAutoConfiguration class is to use the @enableAspectJAutoProxy annotation based on the configuration parameters. The annotation also explains:
This class is enabled only if the configuration parameter spring.aop.auto is not false and we have the configuration in spring-configuration-metadata.json:
AopAutoConfiguration contains the following two built-in configuration classes, corresponding to the configuration parameters spring. AopAutoConfiguration. Proxy-target-class =true/false:
Proxy-target-class is also true when configured by default, which is true in our spring-boot, so cglib proxies are used by default.
At this point, we have a pretty good idea of why the AOP used in Spring-cache always uses the Cglib proxy.
With cglib, we can finally get back to the topic of “why you can’t use cache annotations on private methods”. If you look at it from an AOP perspective, the answer is: Cglib.
Cglib implements dynamic proxy, and its bottom layer uses ASM bytecode generation framework, which directly operates on the bytecode of the class requiring proxy, generates a subclass of the class, and rewrites all the methods of the class that can be rewritten.
Since cglib’s proxy classes use inheritance, this means that Cglib cannot delegate to final classes, nor can it delegate to private methods! Subclasses cannot override private methods.
Spring Cache is implemented using AOP, and AOP does not support interception of private methods. Therefore, Spring cache annotations on private methods are not supported.
Class internal method calls do not support caching
From the above analysis, spring CaHE’s caching capability is due to the use of AOP, so we know that our class is re-proxied by Cglib.
If it’s an internal class method call, why doesn’t it work?
This problem is very simple, we call the internal method of the breakpoint, we can see:
Right? How can caching be used without a proxy?
conclusion
I’m Tin, an ordinary siege lion trying to make himself better. My experience is limited, knowledge is shallow, if you find something wrong with the article, you are very welcome to add me, I will carefully review and modify.
It is not easy to persist in creation. Your positive feedback is the most powerful motivation for me to persist in output. Thank you!
Attach a link to the original text:
Mp.weixin.qq.com/s?__biz=MzI…