Detailed explanation of SpringCache framework loading and interception principle
- 2021-07-22 09:53:57
- OfStack
Official website document
Background
The project A requires an implementation of multiple data sources. For example, UserDao. getAllUserList () needs to be read from the readonly library, but UserDao. insert () needs to be inserted into the main (write) library
You need to add annotations to the method calls in the dao layer!
After understanding, we know-the interface is generated dynamically through jdk proxy (mapper interface of mybatis is generated dynamically through jdk proxy-
>
MapperFactoryBean. class), cannot be intercepted by aop (intercepted by annotation configuration)
//dao
@Pointcut("@annotation(com.kaola.cs.data.common.aspect.DataSourceSelect)")
public void dao() {
}
Then I happened to come into contact with the project B and used the SpringCache module, but the Cache module of Spring was able to intercept it (spring-cache was also intercepted by annotations! ! ! )
Aroused my interest, I turned over the source code once
Use of SpringCache
Compared with mybatis
1. spring-cache is based on the method level of spring, which means that you don't care what your method does, it is only responsible for caching the method results
The cache of mybatis (CachingExecutor/BaseExecutor) is a cache based on database query results
2. spring-cache can be configured with various types of cache media (redis, ehcache, hashmap, even db, and so on)-
>
It only provides interface and default implementation, and can be extended by itself
The cache of mybatis is hashmap, single 1! ! lowb
Configuration of SpringCache
1. Notes (spring-boot) 2. xml configuration
Here we only talk about annotations, but the initialized classes are all 1! ! !
Defining CacheConfigure. java can be used directly
@EnableCaching
@Configuration
public class CacheConfigure extends CachingConfigurerSupport {
@Override
@Bean
public CacheManager cacheManager() {
SimpleCacheManager result = new SimpleCacheManager();
List<Cache> caches = new ArrayList<>();
caches.add(new ConcurrentMapCache("testCache"));
result.setCaches(caches);
return result;
}
@Override
@Bean
public CacheErrorHandler errorHandler() {
return new SimpleCacheErrorHandler();
}
}
The core classes initialized by Spring-Cache can be found through the @ EnableCaching annotation
ProxyCachingConfiguration.java
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource());
advisor.setAdvice(cacheInterceptor());
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheInterceptor cacheInterceptor() {
CacheInterceptor interceptor = new CacheInterceptor();
interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
interceptor.setCacheOperationSource(cacheOperationSource());
return interceptor;
}
}
Instantiate the bean of three classes through annotations: BeanFactoryCacheOperationSourceAdvisor, CacheOperationSource, CacheInterceptor
Say the role of these three classes under 1
BeanFactoryCacheOperationSourceAdvisor.java
/*
BeanFactoryCacheOperationSourceAdvisor Inherit AbstractBeanFactoryPointcutAdvisor
In spring The effect in is that in every bean Initialization of ( Each bean Will be loaded into advised Object -> Have targetSource And Advisor[] Array )
Each bean When the method is called, it is traversed first advisor Method, and then call the native bean( That is targetSource) The method of, implements the aop The effect of
bean When loading BeanFactoryCacheOperationSourceAdvisor Adj. getPointcut()-> That is CacheOperationSourcePointcut Will be obtained, and then call the
CacheOperationSourcePointcut.matches() Method , Used to match the corresponding bean
Hypothesis bean In BeanFactoryCacheOperationSourceAdvisor In the scan of matchs() Method returns the true
The result is that
In every bean When the method of is called CacheInterceptor In invoke() Method is called
Summary:
spring-cache It's also finished aop1 Sample implementation (spring-aop It's the same thing )
The point is that CacheOperationSourcePointcut.matchs() Method, how to match the interface Let's not talk about the specific introduction here! ! ! !
*/
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
@Nullable
private CacheOperationSource cacheOperationSource;
private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
@Override
@Nullable
protected CacheOperationSource getCacheOperationSource() {
return cacheOperationSource;
}
};
/**
* Set the cache operation attribute source which is used to find cache
* attributes. This should usually be identical to the source reference
* set on the cache interceptor itself.
*/
public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
this.cacheOperationSource = cacheOperationSource;
}
/**
* Set the {@link ClassFilter} to use for this pointcut.
* Default is {@link ClassFilter#TRUE}.
*/
public void setClassFilter(ClassFilter classFilter) {
this.pointcut.setClassFilter(classFilter);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
}
CacheOperationSource. java is an interface
The implementation class is-
>
AnnotationCacheOperationSource. java focuses on the parent class--
>
AbstractFallbackCacheOperationSource.java
Explain 1:
The amount of code is very small, mainly the encapsulation of attributeCache, which is used by combining method-CacheOperation
Then get method-class through invocation at CacheInterceptor. invoke () and then call CacheOperationSource. getCacheOperations () to get CacheOperation
CacheOperation actually triggers the operation corresponding to spring-cache annotation-the implementation of obtaining cache
public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource {
/**
* Canonical value held in cache to indicate no caching attribute was
* found for this method and we don't need to look again.
*/
private static final Collection<CacheOperation> NULL_CACHING_ATTRIBUTE = Collections.emptyList();
/**
* Logger available to subclasses.
* <p>As this base class is not marked Serializable, the logger will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
protected final Log logger = LogFactory.getLog(getClass());
/**
* Cache of CacheOperations, keyed by method on a specific target class.
* <p>As this base class is not marked Serializable, the cache will be recreated
* after serialization - provided that the concrete subclass is Serializable.
*/
private final Map<Object, Collection<CacheOperation>> attributeCache = new ConcurrentHashMap<>(1024);
/**
* Determine the caching attribute for this method invocation.
* <p>Defaults to the class's caching attribute if no method attribute is found.
* @param method the method for the current invocation (never {@code null})
* @param targetClass the target class for this invocation (may be {@code null})
* @return {@link CacheOperation} for this method, or {@code null} if the method
* is not cacheable
*/
@Override
@Nullable
public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
return null;
}
Object cacheKey = getCacheKey(method, targetClass);
Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);
if (cached != null) {
return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
}
else {
Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
if (cacheOps != null) {
if (logger.isTraceEnabled()) {
logger.trace("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
}
this.attributeCache.put(cacheKey, cacheOps);
}
else {
this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
}
return cacheOps;
}
}
/**
* Determine a cache key for the given method and target class.
* <p>Must not produce same key for overloaded methods.
* Must produce same key for different instances of the same method.
* @param method the method (never {@code null})
* @param targetClass the target class (may be {@code null})
* @return the cache key (never {@code null})
*/
protected Object getCacheKey(Method method, @Nullable Class<?> targetClass) {
return new MethodClassKey(method, targetClass);
}
@Nullable
private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class.
Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
if (opDef != null) {
return opDef;
}
// Second try is the caching operation on the target class.
opDef = findCacheOperations(specificMethod.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
opDef = findCacheOperations(method);
if (opDef != null) {
return opDef;
}
// Last fallback is the class of the original method.
opDef = findCacheOperations(method.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
}
return null;
}
/**
* Subclasses need to implement this to return the caching attribute for the
* given class, if any.
* @param clazz the class to retrieve the attribute for
* @return all caching attribute associated with this class, or {@code null} if none
*/
@Nullable
protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz);
/**
* Subclasses need to implement this to return the caching attribute for the
* given method, if any.
* @param method the method to retrieve the attribute for
* @return all caching attribute associated with this method, or {@code null} if none
*/
@Nullable
protected abstract Collection<CacheOperation> findCacheOperations(Method method);
/**
* Should only public methods be allowed to have caching semantics?
* <p>The default implementation returns {@code false}.
*/
protected boolean allowPublicMethodsOnly() {
return false;
}
}
! ! ! ! matchs () method of CacheOperationSourcePointcut. java
It is used to determine whether the class meets the spring-cache interception conditions, that is, how to identify the annotations of @ Cachable @ CachePut and so on
The tracing code found that it was called by AnnotationCacheOperationSource. findCacheOperations ()
Omit part of the code....
public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {
private final Set<CacheAnnotationParser> annotationParsers;
@Override
@Nullable
protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
}
@Override
@Nullable
protected Collection<CacheOperation> findCacheOperations(Method method) {
return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
}
/**
* Determine the cache operation(s) for the given {@link CacheOperationProvider}.
* <p>This implementation delegates to configured
* {@link CacheAnnotationParser CacheAnnotationParsers}
* for parsing known annotations into Spring's metadata attribute class.
* <p>Can be overridden to support custom annotations that carry caching metadata.
* @param provider the cache operation provider to use
* @return the configured caching operations, or {@code null} if none found
*/
@Nullable
protected Collection<CacheOperation> determineCacheOperations(CacheOperationProvider provider) {
Collection<CacheOperation> ops = null;
for (CacheAnnotationParser annotationParser : this.annotationParsers) {
Collection<CacheOperation> annOps = provider.getCacheOperations(annotationParser);
if (annOps != null) {
if (ops == null) {
ops = annOps;
}
else {
Collection<CacheOperation> combined = new ArrayList<>(ops.size() + annOps.size());
combined.addAll(ops);
combined.addAll(annOps);
ops = combined;
}
}
}
return ops;
}
}
Then there is the annotation parsing method SpringCacheAnnotationParser. java
The code is very simple.-I won't say much
@Nullable
private Collection<CacheOperation> parseCacheAnnotations(
DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
Collection<? extends Annotation> anns = (localOnly ?
AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
if (anns.isEmpty()) {
return null;
}
final Collection<CacheOperation> ops = new ArrayList<>(1);
anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
anns.stream().filter(ann -> ann instanceof CachePut).forEach(
ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
anns.stream().filter(ann -> ann instanceof Caching).forEach(
ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
return ops;
}
Summarize
1. spring-cache realizes that AbstractBeanFactoryPointcutAdvisor provides CacheOperationSourcePointcut (PointCut) for tangent point judgment and CacheInterceptor (MethodInterceptor) for method interception
2. spring-cache provides CacheOperationSource as the query and load of method corresponding to CacheOperation (cache operation)
3. spring-cache uses SpringCacheAnnotationParser to resolve self-defined annotation classes such as @ Cacheable @ CacheEvict @ Caching
Therefore, spring-cache does not use aspectj, and CacheOperationSource. getCacheOperations () can make the class of jdk proxy match
Matching of classes for jdk proxies
Code classes in CacheOperationSource. getCacheOperations ()
Focus on targetClass and method. If it is the corresponding dao. xxx (), it can matchs () and intercept
CacheInterceptor -
>
CacheAspectSupport. execute () Method
// See the code for yourself. It's also very simple -> The result is that spring-cache It can also be intercepted mybatis Adj. dao Layer interface for caching
@Nullable
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();
}