SpringCache framework loading/intercepting principles
My github blog address
Official document
Docs. Spring. IO/spring/docs…
background
A multi-data source implementation is required in project A, such as userdao.getallUserList (), which needs to be implemented fromreadonlyUserdao.insert () needs to be inserted into the main (write) library by adding annotations to dao layer method calls!Copy the code
Mapperfactorybean.class is implemented by JDK proxy, and can not be intercepted by AOP.
//dao
@Pointcut("@annotation(com.kaola.cs.data.common.aspect.DataSourceSelect)")
public void dao() {}Copy the code
Then I happened to touch project B and used the SpringCache module, but the SpringCache module was able to intercept it (also via annotations!!).
Aroused my interest, turned over the source code again
The purpose of the SpringCache
1. Spring-cache is based on the method level of Spring, which means that you don't care what the method does. It is only responsible for caching method results. Mybatis cache (Cache Executor/BaseExecutor) is a cache based on database query results. Hashmap, even DB etc.) -> It just provides the interface and default implementation, can extend mybatis cache is hashmap, single!! lowbCopy the code
SpringCache configuration
1. Annotations (spring-boot) 2
Only comments here, but the initialized classes are the same!!
Define Cacheconfigure.java and use it 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() {
returnnew SimpleCacheErrorHandler(); }}Copy the code
The core class for spring-Cache initialization can be found using 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());
returninterceptor; }}Copy the code
Through annotation, the three classes of bean instantiation: BeanFactoryCacheOperationSourceAdvisor, CacheOperationSource, CacheInterceptor
Let’s talk about what these three classes do
BeanFactoryCacheOperationSourceAdvisor.java
/ * BeanFactoryCacheOperationSourceAdvisor inherited AbstractBeanFactoryPointcutAdvisor effect is in the spring, At the initialization of each bean (each bean will be loaded as a advised object -> with targetSource and Advisor[] array) each bean is called with the Advisor's methods first iterated, After calling the method of the native bean(aka targetSource), Achieve the effect of aop bean when loading BeanFactoryCacheOperationSourceAdvisor getPointcut () - > is CacheOperationSourcePointcut will be available, Then call CacheOperationSourcePointcut. Matches () method, Used to match the corresponding bean bean hypothesis in the scanning of BeanFactoryCacheOperationSourceAdvisor matchs () method returnstrueThe result is that the invoke() method in the CacheInterceptor is called every time a bean's method is called. Spring - as the implementation of the cache also completed the aop (spring aop is also to do so). Emphasis is on CacheOperationSourcePointcut matchs () method, how to match the interface Behind regardless of the specific introduction here!!!!!!!!!! */ public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { @Nullable private CacheOperationSource cacheOperationSource; private final CacheOperationSourcePointcut pointcut = newCacheOperationSourcePointcut() {
@Override
@Nullable
protected CacheOperationSource getCacheOperationSource() {
returncacheOperationSource; }}; /** * Set the cache operation attributesource 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() {
returnthis.pointcut; }}Copy the code
CacheOperationSource. Java is a interface
Implementation class is – > AnnotationCacheOperationSource. Java is focused on the parent class – > AbstractFallbackCacheOperationSource. Java
Explain:
There is very little code, mainly used to encapsulate attributeCache, Invocation method-class by invocation method-CacheOperation and then invocation method-class from cacheInterceptor.invoke () CacheOperationSource. GetCacheOperations () to obtain CacheOperation CacheOperation is actually trigger corresponding spring - cache annotation operation - for the realization of the cacheCopy the code
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
NULL_CACHING_ATTRIBUTE = Collections.emptyList(); /** * Logger available to subclasses. *
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. *
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
,>
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} ifthe 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})
* @returnthe cache key (never {@code null}) */ protected Object getCacheKey(Method method, @Nullable Class<? > targetClass) {returnnew 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
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
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
findCacheOperations(Method method); /** * Should only public methods be allowed to have caching semantics? *
The default implementation returns {@code false}. */ protected boolean allowPublicMethodsOnly() { return false; }}
Copy the code
!!!!!!!!!! CacheOperationSourcePointcut. Java matchs () method
Where are the annotations that tell if a class is spring-cache compliant @Cachable @CachePut and so on
After tracking code found is AnnotationCacheOperationSource findCacheOperations () call
Omit some 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. * 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
determineCacheOperations(CacheOperationProvider provider) { Collection
ops = null; for (CacheAnnotationParser annotationParser : this.annotationParsers) { Collection
annOps = provider.getCacheOperations(annotationParser); if (annOps ! = null) { if (ops == null) { ops = annOps; } else { Collection
combined = new ArrayList<>(ops.size() + annOps.size()); combined.addAll(ops); combined.addAll(annOps); ops = combined; } } } return ops; }}
Copy the code
Then is annotated parsing methods SpringCacheAnnotationParser. Java
The code is simple. – I won’t go into that
@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;
}
Copy the code
conclusion
1. The spring - cache implements AbstractBeanFactoryPointcutAdvisor CacheOperationSourcePointcut (PointCut) as a point of tangency judgment, CacheInterceptor (MethodInterceptor) 2. Spring-cache provides CacheOperationSource as method corresponding to CacheOperation. Query and load 3. Spring - the cache by SpringCacheAnnotationParser to parse the self-defining @ Cacheable @ CacheEvict @ Caching annotation class, etc So the spring - the cache does not use Aspectj's way, through CacheOperationSource getCacheOperations () method can make the JDK proxy class also can matchCopy the code
Matches the classes of the JDK proxy
Code class in CacheOperationSource. GetCacheOperations ()
The focus is on targetClass and method, which can be matchs() and intercepted if it is dao. XXX ()
CacheInterceptor – > CacheAspectSupport. The execute () method
// See for yourself. The result is that spring-cache can also intercept the DAO layer interface of Mybatis. @nullable protected Object execute(CacheOperationInvoker, Object target, Method Method, Object[] args) { // Check whether aspect is enabled (to cope with caseswhere 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)) {returnexecute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass)); }}}return invoker.invoke();
}
Copy the code