preface
The previous chapters learned the following:
- Cache manager related abstractions
- Annotations and attributes related to cache operation
- The CacheOperation corresponding to the cache operation is related to the CacheOperationSource responsible for resolving the corresponding CacheOperations
The above content paves the way for an in-depth understanding of Spring Cache
Generally, the Spring Cache capability is enabled based on @ EnableCaching. This chapter starts with @ EnableCaching to understand the overall implementation process of Spring Cache. It should be more or less thought that it is implemented based on Spring AOP
@EnableCaching
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(CachingConfigurationSelector.class) public @interface EnableCaching { // Determines whether to base on CGLIB proxy. The default is false boolean proxyTargetClass() default false; // Notification method: PROXY or ASPECTJ. The default PROXY is based on PROXY AdviceMode mode() default AdviceMode.PROXY; // You can sort based on annotations int order() default Ordered.LOWEST_PRECEDENCE; }
- The proxyTargetClass property determines whether to base on CGLIB proxy (when mode == PROXY)
- The mode attribute is the notification method. By default, it is agent-based
- The order attribute description supports annotation based sorting
- The key is that @ Import(CachingConfigurationSelector.class) introduces the configuration class CachingConfigurationSelector
CachingConfigurationSelector
AdviceModeImportSelector
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector { // ... @Override public final String[] selectImports(AnnotationMetadata importingClassMetadata) { // Get generic annotation class Class<?> annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class); // Gets the properties of the annotation AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType); // Get mode property AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName()); // Subclasses are configuration classes introduced based on mode String[] imports = selectImports(adviceMode); return imports; } /** * Some subclasses implement the introduction of corresponding configuration classes based on the mode attribute */ @Nullable protected abstract String[] selectImports(AdviceMode adviceMode); }
AdviceModeImportSelector, which introduces the base class of the corresponding configuration class based on the advicemode attribute of the annotation:
- It is an ImportSelector, so it supports the introduction of configuration classes. Here, the advicemode property will be resolved based on the annotated property
- The selectImports method is handed over to subclasses, and the corresponding configuration class is introduced based on the mode attribute. Spring async and spring cache have corresponding implementations
- For example, the cacheingconfigurationselector introduced by @ EnableCaching above is its introduction to the Spring Cache configuration class
CachingConfigurationSelector
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> { // ... @Override public String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return getProxyImports(); case ASPECTJ: return getAspectJImports(); default: return null; } } private String[] getProxyImports() { List<String> result = new ArrayList<>(3); // Introducing autoproxyregister and ProxyCachingConfiguration result.add(AutoProxyRegistrar.class.getName()); result.add(ProxyCachingConfiguration.class.getName()); return StringUtils.toStringArray(result); } // ... }
- In general, the value of mode is PROXY, which is based on PROXY rather than AspectJ implementation
- getProxyImports introduces two core configuration classes of Spring Cache, autoproxyregister and ProxyCachingConfiguration
AutoProxyRegistrar
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar { // ... @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean candidateFound = false; // All annotations of the import source Set<String> annTypes = importingClassMetadata.getAnnotationTypes(); for (String annType : annTypes) { // Gets the properties of the target annotation AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType); if (candidate == null) { continue; } // Gets the mode and proxyTargetClass attributes of the annotation Object mode = candidate.get("mode"); Object proxyTargetClass = candidate.get("proxyTargetClass"); if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() && Boolean.class == proxyTargetClass.getClass()) { candidateFound = true; if (mode == AdviceMode.PROXY) { // Register an infrastructure advisor autoproxycreator beandefinition AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); // Specify CGLIB proxy if proxyTargetClass == true if ((Boolean) proxyTargetClass) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); return; } } } } } }
- It is an ImportBeanDefinitionRegistrar, so it can hold the metadata information of the source configuration class and register the corresponding BeanDefinition based on this
- Ignoring the details, an infrastructure advisor autoproxycreator will eventually be registered here based on the aopconfigutils#registerautoproxycreator ifnecessary method
InfrastructureAdvisorAutoProxyCreator: AbstractAdvisorAutoProxyCreator The implementation class of the container, which will collect all the data in the container ROLE == ROLE_INFRASTRUCTURE of Advisor,Act as an agent
AopConfigUtils: The function of this class is to register the corresponding AbstractAdvisorAutoProxyCreator, It supports priority override, and the priority is as follows: InfrastructureAdvisorAutoProxyCreator AspectJAwareAdvisorAutoProxyCreator AnnotationAwareAspectJAutoProxyCreator For example: if the container is already registered AspectJAwareAdvisorAutoProxyCreator,Then re register InfrastructureAdvisorAutoProxyCreator Not downgraded, but registered AnnotationAwareAspectJAutoProxyCreator Will upgrade
about InfrastructureAdvisorAutoProxyCreator More details can be found at To view the link:
[source code] interpretation of Spring AOP 13 principle II
about AopConfigUtils More details can be found below
[source code] Spring AOP 14 principle interpretation III
ProxyCachingConfiguration
AbstractCachingConfiguration
@Configuration(proxyBeanMethods = false) public abstract class AbstractCachingConfiguration implements ImportAware { @Nullable protected AnnotationAttributes enableCaching; @Nullable protected Supplier<CacheManager> cacheManager; @Nullable protected Supplier<CacheResolver> cacheResolver; @Nullable protected Supplier<KeyGenerator> keyGenerator; @Nullable protected Supplier<CacheErrorHandler> errorHandler; @Autowired void setConfigurers(ObjectProvider<CachingConfigurer> configurers) { // Collect all user-defined cacheingconfigurers in the container Supplier<CachingConfigurer> configurer = () -> { List<CachingConfigurer> candidates = configurers.stream().collect(Collectors.toList()); // It's ok if there is none if (CollectionUtils.isEmpty(candidates)) { return null; } // Only one is allowed if (candidates.size() > 1) { throw new IllegalStateException("..."); } return candidates.get(0); }; // Configure properties based on the provided CachingConfigurer useCachingConfigurer(new CachingConfigurerSupplier(configurer)); } // ... }
- The abstract configuration base class of Spring Cache, which is mainly used to collect the only cacheingconfigurator in the container and configure properties such as cacheManager cacheResolver
- This mode of managing configuration classes is very common in Spring. For example, Spring Async is also configured by the only asyncconfigurator in the collection container of the configuration base class AbstractAsyncConfiguration
- Therefore, the configuration class provided by us can directly implement CachingConfigurer or inherit CachingConfigurerSupport class to provide the configuration of properties
ProxyCachingConfiguration
@Configuration(proxyBeanMethods = false) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public class ProxyCachingConfiguration extends AbstractCachingConfiguration { @Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor( CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) { BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor(); // The corresponding Pointcut will be created based on the cacheOperationSource advisor.setCacheOperationSource(cacheOperationSource); // Specific Advice is specified advisor.setAdvice(cacheInterceptor); if (this.enableCaching != null) { advisor.setOrder(this.enableCaching.<Integer>getNumber("order")); } return advisor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public CacheOperationSource cacheOperationSource() { // It's ok if the method is not public return new AnnotationCacheOperationSource(false); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) { CacheInterceptor interceptor = new CacheInterceptor(); /** * These properties can be configured in the parent class based on the custom CachingConfigurer */ interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager); interceptor.setCacheOperationSource(cacheOperationSource); return interceptor; } }
This is the final configuration class, which provides the container with the following components:
- Beanfactory cacheoperationsourceadvisor, Advisor for caching behavior agent, based on the understanding of Spring AOP topic: Advisor = Pointcut + Advice, and then we will have an in-depth understanding of Pointcut and Advice corresponding to beanfactory cacheoperationsourceadvisor
- A CacheOperationSource is registered, that is, the annotation CacheOperationSource of the corresponding CacheOperation is parsed based on the annotation, which has been understood in the previous chapter
- A CacheInterceptor is registered based on the corresponding attributes (these attributes support custom configuration in the parent class), which plays the role of Advice, that is, the implementation of cache behavior, which will be discussed separately in the next chapter
BeanFactoryCacheOperationSourceAdvisor
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { @Nullable private CacheOperationSource cacheOperationSource; // Specifies the CacheOperationSourcePointcut of the specific CacheOperationSource private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() { @Override @Nullable protected CacheOperationSource getCacheOperationSource() { return cacheOperationSource; } }; // ... }
- It is an abstractbeanfactory pointcutadvisor. If the Advice is not specified, the abstractbeanfactory pointcutadvisor can obtain the corresponding Advice from the container based on the specified beanName
- At the same time, it supports the designation of Pointcut
- We have learned that it specifies Advice as CacheInterceptor
- The Pointcut specified here is a CacheOperationSourcePointcut created based on CacheOperationSource
- For more details about Advisor, please refer to the following:
[source code] Spring AOP 5 Advisor
CacheOperationSourcePointcut
/** * StaticMethodMatcherPointcut It's a MethodMatcher and a Pointcut * Therefore, the ability of ClassFilter can be expanded based on Pointcut */ abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { // Specify ClassFilter protected CacheOperationSourcePointcut() { setClassFilter(new CacheOperationSourceClassFilter()); } // MethodMatcher#match delegate CacheOperationSource implementation @Override public boolean matches(Method method, Class<?> targetClass) { CacheOperationSource cas = getCacheOperationSource(); return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass))); } //... // The implementation class specifies the specific CacheOperationSource @Nullable protected abstract CacheOperationSource getCacheOperationSource(); // The internal class maintains the corresponding ClassFilter private class CacheOperationSourceClassFilter implements ClassFilter { // ClassFilter#match delegate CacheOperationSource implementation @Override public boolean matches(Class<?> clazz) { // Do not proxy CacheManager if (CacheManager.class.isAssignableFrom(clazz)) { return false; } // Filtering based on CacheOperationSource#isCandidateClass CacheOperationSource cas = getCacheOperationSource(); return (cas == null || cas.isCandidateClass(clazz)); } } }
- It is a StaticMethodMatcherPointcut, so it additionally extends the ability of ClassFilter and allows you to specify a ClassFilter
- As a StaticMethodMatcher, its method matching is based on the CacheOperationSource method: that is, the CacheOperationSource can only match when it can resolve to CacheOperation on the target method
- The specified ClassFilter is the internal class CacheOperationSourceClassFilter, and its matching is completed by the delegate CacheOperationSource#isCandidateClass
- For more details about Pointcut, see below
[source code] Spring AOP 4 Pointcut
demo
public class BeanFactoryCacheOperationSourceAdvisorDemo { public static class CacheTarget { @Cacheable public String a(String a) { return "a"; } @CachePut public String b(String b) { return "b"; } } @Test public void test() throws NoSuchMethodException { BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor(); advisor.setCacheOperationSource(new AnnotationCacheOperationSource()); // Classfilters #matches are implemented by delegating AnnotationCacheOperationSource System.out.println(advisor.getPointcut() .getClassFilter() .matches(CacheTarget.class)); // MethodMatcher#matches is implemented by delegating AnnotationCacheOperationSource System.out.println(advisor.getPointcut() .getMethodMatcher() .matches( CacheTarget.class.getMethod("a", String.class) , CacheTarget.class )); } }
Experience the capabilities of the pointcut part of beanfactory cacheoperationsourceadvisor with examples
summary
Starting with @ EnableCaching, this chapter is roughly summarized as follows:
- It introduces the configuration class CachingConfigurationSelector, and then introduces the configuration classes autoproxyregister and ProxyCachingConfiguration
- Autoproxyregister registers an infrastructure Advisor autoproxycreator, which supports the proxy of the Advisor specified in the collection container at the corresponding stage of the bean life cycle
- ProxyCachingConfiguration provides the registration of the above Advisor components
- The Advisor registered for Spring Cache is beanfactory cacheoperationsourceadvisor. The Pointcut it provides is a CacheOperationSourcePointcut created based on CacheOperationSource
- The filtering of classes and methods by CacheOperationSourcePointcut is naturally based on CacheOperationSource. The instance registered here is AnnotationCacheOperationSource, which has been learned before
- BeanFactoryCacheOperationSourceAdvisor specifies that Advice is a CacheInterceptor, which implements the logic of agent behavior (i.e. Cache). There are many contents, which will be understood separately in the next chapter
Previous article: [Spring Cache] four cacheoperations cacheoperationsource cacheannotation parser