[Spring Cache] V understand the implementation process of Spring Cache from @ EnableCaching

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

Next: six cacheinterceptors related to [Spring Cache]

Keywords: Java Spring Cache

Added by rashmi_k28 on Thu, 03 Feb 2022 10:52:31 +0200