An overview,

Since Spring3.1, the Spring framework has supported explicitly adding caching to existing Spring applications. Like transaction support, caching abstraction allows for consistent use of various caching solutions with minimal intrusion into code. Spring caching is implemented in the Spring-Context package, or in spring-boot-Autoconfige if programming is based on the Springboot infrastructure There are a number of default configurations and definitions that enable the cache capability to a greater extent without user awareness. If the cache capability provided by the three-party suite is not required, there is no need to introduce additional dependencies. This analysis is based on springboot2.1.3. The principle of cache usage is as follows:

Two, use mode

Spring Cache provides out-of-the-box access, requiring only a few annotations and cache management classes.

1. Enable the cache capability

Introducing cache dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    <version>2.13..RELEASE</version>
</dependency>
Copy the code

Add the @enablecaching annotation to the application startup class:

@SpringBootApplication
@EnableCaching
public class Application {

    public static void main(String[] args) { SpringApplication.run(Application.class, args); }}Copy the code

2. Use caching

Add @cacheable annotation to the business method:

@Cacheable(cacheNames = {"task"})
public TaskInfoDTO getTask(String taskId) {
    log.info("TestBuzz.getTask mock query from DB......");
    TaskInfoDTO taskInfoDTO = new TaskInfoDTO();
    taskInfoDTO.setTaskId(taskId);
    taskInfoDTO.setApplicantId("system");
    taskInfoDTO.setDescription("test");
    return taskInfoDTO;
}
Copy the code

Mock request:

@GetMapping("/test_cache")
public IResp<TaskInfoDTO> testCache(a) {
    TaskInfoDTO taskInfoDTO = this.testBuzz.getTask("task123");
    return IResp.getSuccessResult(taskInfoDTO);
}
Copy the code

Send two query requests consecutively:

curl http://localhost:port/test_cache
Copy the code

You can see from the log that only one DB call was printed, indicating that the cache was removed the second time. That’s how we turned on and used Spring’s caching capabilities.

Three, source code & principle analysis

We analyze the cache principle from the cache modules of spring-Context and spring-boot-Autoconfige packages. Enable cache sequence diagram:Caches notification configuration sequence diagram:Cache notification assembly

1. Enable cache

@EnableCaching

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
	// Whether to proxy the target class directly
	boolean proxyTargetClass(a) default false;
	// Notification mode, default is proxy
	AdviceMode mode(a) default AdviceMode.PROXY;
	/ / order
	int order(a) default Ordered.LOWEST_PRECEDENCE;
}
Copy the code

The annotations and @ EnableAsync annotations especially like that are based on Aop and agent, the class import CachingConfigurationSelector class, we have a look at its implementation:

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {

	private static final String PROXY_JCACHE_CONFIGURATION_CLASS =
			"org.springframework.cache.jcache.config.ProxyJCacheConfiguration";
	private static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME =
			"org.springframework.cache.aspectj.AspectJCachingConfiguration";
	private static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME =
			"org.springframework.cache.aspectj.AspectJJCacheConfiguration";
	private static final boolean jsr107Present;
	private static final boolean jcacheImplPresent;
	static {
		ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
		jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
		jcacheImplPresent = ClassUtils.isPresent(PROXY_JCACHE_CONFIGURATION_CLASS, classLoader);
	}
	@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);
		result.add(AutoProxyRegistrar.class.getName());
		result.add(ProxyCachingConfiguration.class.getName());
		if (jsr107Present && jcacheImplPresent) {
			result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
		}
		return StringUtils.toStringArray(result);
	}
	private String[] getAspectJImports() {
		List<String> result = new ArrayList<>(2);
		result.add(CACHE_ASPECT_CONFIGURATION_CLASS_NAME);
		if (jsr107Present && jcacheImplPresent) {
			result.add(JCACHE_ASPECT_CONFIGURATION_CLASS_NAME);
		}
		returnStringUtils.toStringArray(result); }}Copy the code

One concept that needs to be explained is that JSR107 is a proposal for a Java specification for caching, which requires the introduction of javax.cache packages. CachingConfigurationSelector class is the core of selectImports method, according to the different configuration of @ EnableCaching configuration mode selection of type, the default is the PROXY mode, import AutoProxyRegistrar and ProxyCac HingConfiguration Indicates two configurations.

2. Configure cache notification

The parent class AbstractCachingConfiguration implementation:

@Configuration
public abstract class AbstractCachingConfiguration implements ImportAware {
	@Override
	public void setImportMetadata(AnnotationMetadata importMetadata) {
		this.enableCaching = AnnotationAttributes.fromMap(
				importMetadata.getAnnotationAttributes(EnableCaching.class.getName(), false));
		if (this.enableCaching == null) {
			throw new IllegalArgumentException(
					"@EnableCaching is not present on importing class "+ importMetadata.getClassName()); }}@Autowired(required = false)
	void setConfigurers(Collection<CachingConfigurer> configurers) {
		if (CollectionUtils.isEmpty(configurers)) {
			return;
		}
		if (configurers.size() > 1) {
			throw new IllegalStateException(configurers.size() + " implementations of " +
					"CachingConfigurer were found when only 1 was expected. " +
					"Refactor the configuration such that CachingConfigurer is " +
					"implemented only once or not at all.");
		}
		CachingConfigurer configurer = configurers.iterator().next();
		useCachingConfigurer(configurer);
	}
	protected void useCachingConfigurer(CachingConfigurer config) {
		this.cacheManager = config::cacheManager;
		this.cacheResolver = config::cacheResolver;
		this.keyGenerator = config::keyGenerator;
		this.errorHandler = config::errorHandler; }}Copy the code

Did two things, first, the annotation metadata attributes parsed, and then put the user-defined cache component assembly in (CacheManager that KeyGenerator and exception handler). Watching ProxyCachingConfiguration configuration implementation:

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
	@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(a) {
		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(a) {
		return new AnnotationCacheOperationSource();
	}
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheInterceptor cacheInterceptor(a) {
		CacheInterceptor interceptor = new CacheInterceptor();
		interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
		interceptor.setCacheOperationSource(cacheOperationSource());
		returninterceptor; }}Copy the code

ProxyCachingConfiguration reuse the ability of the parent class and three core components of AOP (pointcuts, Advice, and Advisor), see first AnnotationCacheOperationSource (at this point cannot be called Pointc ut):AnnotationCacheOperationSource inheritance AbstractFallbackCacheOperationSource class implements CacheOperationSource interface, achieve getCacheOperations method Method on the cache comments parsed into cache collection operation, AbstractFallbackCacheOperationSource. GetCacheOperations:

public Collection<CacheOperation> getCacheOperations(Method method, @NullableClass<? > 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);
		}
		returncacheOps; }}Copy the code

CacheKey is a MethodClassKey constructed from method and class, which returns directly if there is a cache operation set in the cache, otherwise computeCacheOperations are called to calculate:

private Collection<CacheOperation> computeCacheOperations(Method method, @NullableClass<? > 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)) {
			returnopDef; }}return null;
}
Copy the code

This method from the target class and the target method on dimension (preferred method) parsing cache annotations assembly into cache operation (@ Cacheable – > CacheableOperation), see a subclass AnnotationCacheOperationSource implementation:

public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
	this.publicMethodsOnly = publicMethodsOnly;
	this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
}
@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));
}

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 = newArrayList<>(ops.size() + annOps.size()); combined.addAll(ops); combined.addAll(annOps); ops = combined; }}}return ops;
}
Copy the code

AnnotationCacheOperationSource the default constructor is used SpringCacheAnnotationParser parser, parsing operations entrusted to SpringCacheAnnotationParser. Eventually parseCacheAnn Otations to parse annotations into their respective operations:

@Override
@Nullable
public Collection<CacheOperation> parseCacheAnnotations(Class
        type) {
	DefaultCacheConfig defaultConfig = new DefaultCacheConfig(type);
	return parseCacheAnnotations(defaultConfig, type);
}
@Override
@Nullable
public Collection<CacheOperation> parseCacheAnnotations(Method method) {
	DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass());
	return parseCacheAnnotations(defaultConfig, method);
}
@Nullable
private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
	Collection<CacheOperation> ops = parseCacheAnnotations(cachingConfig, ae, false);
	if(ops ! =null && ops.size() > 1) {
		// More than one operation found -> local declarations override interface-declared ones...
		Collection<CacheOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true);
		if(localOps ! =null) {
			returnlocalOps; }}return ops;
}
@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;
}
private CacheableOperation parseCacheableAnnotation( AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {
	CacheableOperation.Builder builder = new CacheableOperation.Builder();
	builder.setName(ae.toString());
	builder.setCacheNames(cacheable.cacheNames());
	builder.setCondition(cacheable.condition());
	builder.setUnless(cacheable.unless());
	builder.setKey(cacheable.key());
	builder.setKeyGenerator(cacheable.keyGenerator());
	builder.setCacheManager(cacheable.cacheManager());
	builder.setCacheResolver(cacheable.cacheResolver());
	builder.setSync(cacheable.sync());
	defaultConfig.applyDefault(builder);
	CacheableOperation op = builder.build();
	validateCacheOperation(ae, op);
	return op;
}
private CacheEvictOperation parseEvictAnnotation( AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {
	CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();
	builder.setName(ae.toString());
	builder.setCacheNames(cacheEvict.cacheNames());
	builder.setCondition(cacheEvict.condition());
	builder.setKey(cacheEvict.key());
	builder.setKeyGenerator(cacheEvict.keyGenerator());
	builder.setCacheManager(cacheEvict.cacheManager());
	builder.setCacheResolver(cacheEvict.cacheResolver());
	builder.setCacheWide(cacheEvict.allEntries());
	builder.setBeforeInvocation(cacheEvict.beforeInvocation());
	defaultConfig.applyDefault(builder);
	CacheEvictOperation op = builder.build();
	validateCacheOperation(ae, op);
	return op;
}

private CacheOperation parsePutAnnotation( AnnotatedElement ae, DefaultCacheConfig defaultConfig, CachePut cachePut) {
	CachePutOperation.Builder builder = new CachePutOperation.Builder();
	builder.setName(ae.toString());
	builder.setCacheNames(cachePut.cacheNames());
	builder.setCondition(cachePut.condition());
	builder.setUnless(cachePut.unless());
	builder.setKey(cachePut.key());
	builder.setKeyGenerator(cachePut.keyGenerator());
	builder.setCacheManager(cachePut.cacheManager());
	builder.setCacheResolver(cachePut.cacheResolver());
	defaultConfig.applyDefault(builder);
	CachePutOperation op = builder.build();
	validateCacheOperation(ae, op);
	return op;
}
private void parseCachingAnnotation( AnnotatedElement ae, DefaultCacheConfig defaultConfig, Caching caching, Collection
       
         ops)
        {
	Cacheable[] cacheables = caching.cacheable();
	for (Cacheable cacheable : cacheables) {
		ops.add(parseCacheableAnnotation(ae, defaultConfig, cacheable));
	}
	CacheEvict[] cacheEvicts = caching.evict();
	for (CacheEvict cacheEvict : cacheEvicts) {
		ops.add(parseEvictAnnotation(ae, defaultConfig, cacheEvict));
	}
	CachePut[] cachePuts = caching.put();
	for(CachePut cachePut : cachePuts) { ops.add(parsePutAnnotation(ae, defaultConfig, cachePut)); }}Copy the code

Then look at the notification CacheInterceptor:CacheInterceptor implements Advice and extends CacheAspectSupport. Here are some implementations:

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor.Serializable {
	@Override
	@Nullable
	public Object invoke(final MethodInvocation invocation) throws Throwable {
		Method method = invocation.getMethod();
		CacheOperationInvoker aopAllianceInvoker = () -> {
			try {
				return invocation.proceed();
			}
			catch (Throwable ex) {
				throw newCacheOperationInvoker.ThrowableWrapper(ex); }};try {
			return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
		}
		catch (CacheOperationInvoker.ThrowableWrapper th) {
			throwth.getOriginal(); }}}Copy the code

When called intercepted are encapsulated into CacheOperationInvoker and perform to the parent, and parent CacheAspectSupport SmartInitializingSingleton interface is achieved, the singleton container will invoke afterSingleto after initialization NsInstantiated method:

public void afterSingletonsInstantiated(a) {
	if (getCacheResolver() == null) {
		// Lazily initialize cache resolver via default cache manager...
		Assert.state(this.beanFactory ! =null."CacheResolver or BeanFactory must be set on cache aspect");
		try {
			setCacheManager(this.beanFactory.getBean(CacheManager.class));
		}
		catch (NoUniqueBeanDefinitionException ex) {
			throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " +
					"CacheManager found. Mark one as primary or declare a specific CacheManager to use.");
		}
		catch (NoSuchBeanDefinitionException ex) {
			throw new IllegalStateException("No CacheResolver specified, and no bean of type CacheManager found. " +
					"Register a CacheManager bean or remove the @EnableCaching annotation from your configuration."); }}this.initialized = true;
}
Copy the code

Check for proper CacheManager, and will be initialized set to true, continue watching CacheAspectSupport… the execute:

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,
						newCacheOperationContexts(operations, method, args, target, targetClass)); }}}return invoker.invoke();
}
Copy the code

Using AnnotationCacheOperationSource target classes and methods on the cache comments parsed into collection operation, and then construct CacheOperationContexts context and calling overloaded methods:

public CacheOperationContexts(Collection
        operations, Method method, Object[] args, Object target, Class
        targetClass) {
	this.contexts = new LinkedMultiValueMap<>(operations.size());
	for (CacheOperation op : operations) {
        //
		this.contexts.add(op.getClass(), getOperationContext(op, method, args, target, targetClass));
	}
    //
	this.sync = determineSyncFlag(method);
}
Copy the code

Wrapping the context mapping for each operation and checking if it is a synchronous operation (the @cacheable attribute is unique), continue to look at execute:

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
	// Special handling of synchronized invocation
	if (contexts.isSynchronized()) {
		CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
		if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
			Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
			Cache cache = context.getCaches().iterator().next();
			try {
				return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
			}
			catch (Cache.ValueRetrievalException ex) {
				throw(CacheOperationInvoker.ThrowableWrapper) ex.getCause(); }}else {
			returninvokeOperation(invoker); }}// Process any early evictions
	processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
			CacheOperationExpressionEvaluator.NO_RESULT);

	// Check if we have a cached item matching the conditions
	Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

	// Collect puts from any @Cacheable miss, if no cached item is found
	List<CachePutRequest> cachePutRequests = new LinkedList<>();
	if (cacheHit == null) {
		collectPutRequests(contexts.get(CacheableOperation.class),
				CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
	}
	Object cacheValue;
	Object returnValue;

	if(cacheHit ! =null && !hasCachePut(contexts)) {
		// If there are no put requests, just use the cache hit
		cacheValue = cacheHit.get();
		returnValue = wrapCacheValue(method, cacheValue);
	}
	else {
		// Invoke the method if we don't have a cache hit
		returnValue = invokeOperation(invoker);
		cacheValue = unwrapReturnValue(returnValue);
	}

	// Collect any explicit @CachePuts
	collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

	// Process any collected put requests, either from @CachePut or a @Cacheable miss
	for (CachePutRequest cachePutRequest : cachePutRequests) {
		cachePutRequest.apply(cacheValue);
	}

	// Process any late evictions
	processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

	return returnValue;
}
Copy the code

This method is the core logic of the cache operation. First check if it is a synchronous operation (@cacheable feature), call the cache fetch logic and return if it is and conditions are met, otherwise return the business logic without caching invokeOperation, and then perform a front-flush of @cacheevict (beforeInvo) Cationism =true), and then check whether @cacheable has hit the cache. If not, it is put into the list of CachePutRequest to be executed temporarily. Then check whether there is a cache hit and no caches to be updated And populates the results that need to be cached, then collects the @cachePUT operation, synchronizes the @CachePUT and @Cacheable missed requests to the cache, and finally cleans the @cacheevict cache (beforeInvocation=false).

Cache proxy assembly

The previous section covered caching configuration and workflow, so when will the Aop configuration above take effect? Where does it take effect? How does it work? Next, we will look at the assembly logic of the cache agent from the AutoProxyRegistrar point of entry.

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
	private final Log logger = LogFactory.getLog(getClass());
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean candidateFound = false;
		Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
		for (String annoType : annoTypes) {
			AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
			if (candidate == null) {
				continue;
			}
			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) {
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					if ((Boolean) proxyTargetClass) {
						AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
						return;
					}
				}
			}
		}
	}
}
Copy the code

AutoProxyRegistrar implements ImportBeanDefinitionRegistrar interface, registerBeanDefinitions will be extracted from annotations enable cache @ EnableCaching attributes, and then manually register automatic proxy creator:

public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
	return registerAutoProxyCreatorIfNecessary(registry, null);
}

public static BeanDefinition registerAutoProxyCreatorIfNecessary(
		BeanDefinitionRegistry registry, @Nullable Object source) {
	return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

private static BeanDefinition registerOrEscalateApcAsRequired( Class<? > cls, BeanDefinitionRegistry registry,@Nullable Object source) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
		BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
		if(! cls.getName().equals(apcDefinition.getBeanClassName())) {int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
			int requiredPriority = findPriorityForClass(cls);
			if(currentPriority < requiredPriority) { apcDefinition.setBeanClassName(cls.getName()); }}return null;
	}
	RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
	beanDefinition.setSource(source);
	beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
	beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
	registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
	return beanDefinition;
}
Copy the code

Manually signed InfrastructureAdvisorAutoProxyCreator into the container, see InfrastructureAdvisorAutoProxyCreator inheritance relationships:

public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {
	@Nullable
	private ConfigurableListableBeanFactory beanFactory;

	@Override
	protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		super.initBeanFactory(beanFactory);
		this.beanFactory = beanFactory;
	}
	@Override
	protected boolean isEligibleAdvisorBean(String beanName) {
		return (this.beanFactory ! =null && this.beanFactory.containsBeanDefinition(beanName) &&
				this.beanFactory.getBeanDefinition(beanName).getRole() == BeanDefinition.ROLE_INFRASTRUCTURE); }}Copy the code

InfrastructureAdvisorAutoProxyCreator inherited AbstractAdvisorAutoProxyCreator class implements the BeanFactory initialization and isEligibleAdvisorBean method, continue to see A parent BstractAdvisorAutoProxyCreator implementation:

public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {
	@Nullable
	private BeanFactoryAdvisorRetrievalHelper advisorRetrievalHelper;
	protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		this.advisorRetrievalHelper = new BeanFactoryAdvisorRetrievalHelperAdapter(beanFactory);
	}
	@Override
	@Nullable
	protectedObject[] getAdvicesAndAdvisorsForBean( Class<? > beanClass, String beanName,@Nullable TargetSource targetSource) {
		List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
		if (advisors.isEmpty()) {
			return DO_NOT_PROXY;
		}
		return advisors.toArray();
	}
	protected List<Advisor> findEligibleAdvisors(Class
        beanClass, String beanName) {
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		extendAdvisors(eligibleAdvisors);
		if(! eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors); }return eligibleAdvisors;
	}
	protected List<Advisor> findCandidateAdvisors(a) {
		Assert.state(this.advisorRetrievalHelper ! =null."No BeanFactoryAdvisorRetrievalHelper available");
		return this.advisorRetrievalHelper.findAdvisorBeans();
	}
	protected List<Advisor> findAdvisorsThatCanApply( List
       
         candidateAdvisors, Class
         beanClass, String beanName)
        {
		ProxyCreationContext.setCurrentProxiedBeanName(beanName);
		try {
			return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
		}
		finally {
			ProxyCreationContext.setCurrentProxiedBeanName(null); }}protected boolean isEligibleAdvisorBean(String beanName) {
		return true;
	}
	private class BeanFactoryAdvisorRetrievalHelperAdapter extends BeanFactoryAdvisorRetrievalHelper {
		public BeanFactoryAdvisorRetrievalHelperAdapter(ConfigurableListableBeanFactory beanFactory) {
			super(beanFactory);
		}
		@Override
		protected boolean isEligibleBean(String beanName) {
			return AbstractAdvisorAutoProxyCreator.this.isEligibleAdvisorBean(beanName); }}}Copy the code

AbstractAdvisorAutoProxyCreator defines the Advisor tool operation method, and defines the Advisor to extract adapter BeanFactoryAdvisorRetrievalHelperAdapter, entrusted to subclass isEligib LeAdvisorBean method (InfrastructureAdvisorAutoProxyCreator). Focus on AbstractAdvisorAutoProxyCreator parent AbstractAutoProxyCreator postProcessBeforeInstantiation method, this method in InstantiationAwareB EanPostProcessor interface definition, this method in the bean created before the call, if this method returns a null object, then the bean’s creation process will be a short circuit, the role of here is to meet BeanFactoryCacheOperationSourceAdvisor intensifier into logic Class weaving enhanced logic, also known as caching capabilities, to see the method implementation:

public Object postProcessBeforeInstantiation(Class
        beanClass, String beanName) {
	Object cacheKey = getCacheKey(beanClass, beanName);

	if(! StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
		if (this.advisedBeans.containsKey(cacheKey)) {
			return null;
		}
		if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return null; }}// Create proxy here if we have a custom TargetSource.
	// Suppresses unnecessary default instantiation of the target bean:
	// The TargetSource will handle target instances in a custom fashion.
	TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
	if(targetSource ! =null) {
		if (StringUtils.hasLength(beanName)) {
			this.targetSourcedBeans.add(beanName);
		}
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
		Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
		this.proxyTypes.put(cacheKey, proxy.getClass());
		return proxy;
	}

	return null;
}
Copy the code

The logic and AsyncAnnotationBeanPostProcessor postProcessAfterInitialization method is very similar, here are the interceptor bean creation process and the logic, woven into this is automatically generated proxy class and weave into the caching logic. This is also the core logic of the automatic proxy implementation of APC mentioned above. This method first half from the cache access to the target class ever be agent, if the agent directly to enhance the logical weave, avoid repetition and create proxies, during the second half is generated proxy logic, we create a proxy process analysis before, here no longer analysis, focuses on analyzing the obtained from the candidate intensifier getAdvicesAndAdv enhance the logical way isorsForBean:

protectedObject[] getAdvicesAndAdvisorsForBean( Class<? > beanClass, String beanName,@Nullable TargetSource targetSource) {
	List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
	if (advisors.isEmpty()) {
		return DO_NOT_PROXY;
	}
	return advisors.toArray();
}
Copy the code

The method is implemented in subclasses AbstractAdvisorAutoProxyCreator, then call the findEligibleAdvisors method:

protected List<Advisor> findEligibleAdvisors(Class
        beanClass, String beanName) {
	List<Advisor> candidateAdvisors = findCandidateAdvisors();
	List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
	extendAdvisors(eligibleAdvisors);
	if(! eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors); }return eligibleAdvisors;
}
Copy the code

Pass in front of defined BeanFactoryAdvisorRetrievalHelper capture candidate intensifier, and then call findAdvisorsThatCanApply method selecting and apply to the current proxy class enhancer:

protected List<Advisor> findAdvisorsThatCanApply( List
       
         candidateAdvisors, Class
         beanClass, String beanName)
        {

	ProxyCreationContext.setCurrentProxiedBeanName(beanName);
	try {
		return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
	}
	finally {
		ProxyCreationContext.setCurrentProxiedBeanName(null); }}Copy the code

This method handles the filtering logic delegate as the findAdvisorsThatCanApply method of the Aop tool class:

public static List<Advisor> findAdvisorsThatCanApply(List
       
         candidateAdvisors, Class
         clazz)
        {
	if (candidateAdvisors.isEmpty()) {
		return candidateAdvisors;
	}
	List<Advisor> eligibleAdvisors = new ArrayList<>();
	for (Advisor candidate : candidateAdvisors) {
		if (candidate instanceofIntroductionAdvisor && canApply(candidate, clazz)) { eligibleAdvisors.add(candidate); }}booleanhasIntroductions = ! eligibleAdvisors.isEmpty();for (Advisor candidate : candidateAdvisors) {
		if (candidate instanceof IntroductionAdvisor) {
			// already processed
			continue;
		}
		if(canApply(candidate, clazz, hasIntroductions)) { eligibleAdvisors.add(candidate); }}return eligibleAdvisors;
}
Copy the code

From ProxyCachingConfiguration intensifier definition, BeanFactoryCacheOperationSourceAdvisor PointcutAdvisor types, method first half IntroductionAdvisor logic If yes, check whether the conditions are met through canApply. If yes, join the return list:

public static boolean canApply(Advisor advisor, Class<? > targetClass,boolean hasIntroductions) {
	if (advisor instanceof IntroductionAdvisor) {
		return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
	}
	else if (advisor instanceof PointcutAdvisor) {
		PointcutAdvisor pca = (PointcutAdvisor) advisor;
		return canApply(pca.getPointcut(), targetClass, hasIntroductions);
	}
	else {
		// It doesn't have a pointcut so we assume it applies.
		return true; }}Copy the code

Go directly to the second conditional branch and check that the PointcutAdvisor follows the cut logic:

public static boolean canApply(Pointcut pc, Class<? > targetClass,boolean hasIntroductions) {
	Assert.notNull(pc, "Pointcut must not be null");
	if(! pc.getClassFilter().matches(targetClass)) {return false;
	}

	MethodMatcher methodMatcher = pc.getMethodMatcher();
	if (methodMatcher == MethodMatcher.TRUE) {
		// No need to iterate the methods if we're matching any method anyway...
		return true;
	}
	IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
	if (methodMatcher instanceofIntroductionAwareMethodMatcher) { introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; } Set<Class<? >> classes =new LinkedHashSet<>();
	if(! Proxy.isProxyClass(targetClass)) { classes.add(ClassUtils.getUserClass(targetClass)); } classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));for(Class<? > clazz : classes) { Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);for (Method method : methods) {
			if(introductionAwareMethodMatcher ! =null ?
					introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
					methodMatcher.matches(method, targetClass)) {
				return true; }}}return false;
}
Copy the code

This method is not too complex. It simply checks to see if there are cache-related annotations (@cacheable, @cacheput, @cacheevict, etc.) on the target class and method. If there are, find the list of enhancers that are applicable to the target proxy class and call createProxy in APC to create the proxy:

protected Object createProxy(Class<? > beanClass,@Nullable String beanName,
		@Nullable Object[] specificInterceptors, TargetSource targetSource) {
	if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
		AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
	}
	ProxyFactory proxyFactory = new ProxyFactory();
	proxyFactory.copyFrom(this);
	if(! proxyFactory.isProxyTargetClass()) {if (shouldProxyTargetClass(beanClass, beanName)) {
			proxyFactory.setProxyTargetClass(true);
		}
		else {
			evaluateProxyInterfaces(beanClass, proxyFactory);
		}
	}
	Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
	proxyFactory.addAdvisors(advisors);
	proxyFactory.setTargetSource(targetSource);
	customizeProxyFactory(proxyFactory);
	proxyFactory.setFrozen(this.freezeProxy);
	if (advisorsPreFiltered()) {
		proxyFactory.setPreFiltered(true);
	}
	return proxyFactory.getProxy(getProxyClassLoader());
}
Copy the code

Create the proxy factory here, then select whether you want to directly proxy the target class, then assemble the enhancer, and then create the proxy by calling JdkDynamicAopProxy or CglibAopProxy.

4. Configure cache

In the second section of this article, we introduced the cache dependency to enable the cache capability to directly use the cache, without introducing or matching If your application architecture is based on Spring instead of Springboot, you’ll have to configure CacheResolver or CacheManager yourself. Why don’t you do it here, thanks to Spring -boot-autoconfigure, which is introduced explicitly or indirectly when Springboot is used as the base framework.Spring-boot-autoconfigure has a package called cache. This is where Springboot defines and automatically enables the cache Configuration. This package contains *Configuration classes, that is, the cache-related Configuration provided by Springboot Single analyze CacheAutoConfiguration, CacheConfigurations, GenericCacheConfiguration, NoOpCacheConfiguration, SimpleCacheConfiguratio N, CaffeineCacheConfiguration and RedisCacheConfiguration configuration class.CacheConfigurations

final class CacheConfigurations {
	private static finalMap<CacheType, Class<? >> MAPPINGS;static{ Map<CacheType, Class<? >> mappings =new EnumMap<>(CacheType.class);
		mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
		mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
		mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
		mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
		mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
		mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
		mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
		mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
		mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
		mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
		MAPPINGS = Collections.unmodifiableMap(mappings);
	}
	public static String getConfigurationClass(CacheType cacheType) { Class<? > configurationClass = MAPPINGS.get(cacheType); Assert.state(configurationClass ! =null, () - >"Unknown cache type " + cacheType);
		return configurationClass.getName();
	}
	public static CacheType getType(String configurationClassName) {
		for(Map.Entry<CacheType, Class<? >> entry : MAPPINGS.entrySet()) {if (entry.getValue().getName().equals(configurationClassName)) {
				returnentry.getKey(); }}throw new IllegalStateException(
				"Unknown configuration class "+ configurationClassName); }}Copy the code

CacheConfigurations is a cache-configuration cache. The CacheConfigurations definition defines 10 cache-configuration cache types supported by Springboot. The CacheConfigurations definition covers all popular cache types and NoOpCacheConfiguration uration Configuration, which is used when caching is not enabled, and can be understood as a special kind of cache. CacheAutoConfiguration

@Configuration
@ConditionalOnClass(CacheManager.class)
@ConditionalOnBean(CacheAspectSupport.class)
@ConditionalOnMissingBean(value = CacheManager.class, name = "cacheResolver")
@EnableConfigurationProperties(CacheProperties.class)
@AutoConfigureAfter({ CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class })
@Import(CacheConfigurationImportSelector.class)
public class CacheAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public CacheManagerCustomizers cacheManagerCustomizers( ObjectProvider
       
        > customizers)
       > {
		return new CacheManagerCustomizers(
				customizers.orderedStream().collect(Collectors.toList()));
	}
	@Bean
	public CacheManagerValidator cacheAutoConfigurationValidator( CacheProperties cacheProperties, ObjectProvider
       
         cacheManager)
        {
		return new CacheManagerValidator(cacheProperties, cacheManager);
	}
	@Configuration
	@ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
	@ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
	protected static class CacheManagerJpaDependencyConfiguration
			extends EntityManagerFactoryDependsOnPostProcessor {

		public CacheManagerJpaDependencyConfiguration(a) {
			super("cacheManager"); }}}Copy the code

This class is also the entry point to the default Springboot cache configuration. The name of the class has many annotations that restrict startup conditions and assembly rules for the configuration change. @conditionalonClass (CacheManager NditionalOnBean (CacheAspectSupport.class) restricts the application container to have a CacheAspectSupport or subclass instance, @conditionAlonmissingBean (value = CacheManager.class, name = “CacheResolver”) limit application container can not have a type of CacheManager to name cacheResolver bean, if the user custom then the configuration time-outs, @ EnableConfigurationProperties (CachePro Perties. Class) is said to enable cache configuration properties, @ AutoConfigureAfter limit the class in CouchbaseAutoConfiguration, HazelcastAutoConfiguration, HibernateJpaAuto After the Configuration and RedisAutoConfiguration Configuration, @ Import (CacheConfigurationImportSelector. Class) within defined CacheConfigurationImport is introduced The Selector configuration; Look at the CacheConfigurationImportSelector:

/ * * * {@link ImportSelector} to add {@link CacheType} configuration classes.
 */
static class CacheConfigurationImportSelector implements ImportSelector {
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		CacheType[] types = CacheType.values();
		String[] imports = new String[types.length];
		for (int i = 0; i < types.length; i++) {
			imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
		}
		returnimports; }}Copy the code

The class will import CacheType defined in all supported cache type configuration, CacheAutoConfiguration also defines several beans, CacheManagerCustomizers is CacheManager container, CacheManagerValid Ator checks when invoked CacheManager exists and give out definition exception, CacheManager CacheManagerJpaDependencyConfiguration is right depends on the definition of the attributes of the Jpa and post processing. Next, let’s take a look at some of the common cache configurations mentioned above: NoOpCacheConfiguration

@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class NoOpCacheConfiguration {
	@Bean
	public NoOpCacheManager cacheManager(a) {
		return newNoOpCacheManager(); }}Copy the code

NoOpCacheConfiguration Specifies NoOpCacheManager. The CacheManager interface directly invokes the service logic to extract data, which is a dummy cache. SimpleCacheConfiguration

@Configuration
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class SimpleCacheConfiguration {

	private final CacheProperties cacheProperties;

	private final CacheManagerCustomizers customizerInvoker;

	SimpleCacheConfiguration(CacheProperties cacheProperties,
			CacheManagerCustomizers customizerInvoker) {
		this.cacheProperties = cacheProperties;
		this.customizerInvoker = customizerInvoker;
	}
	@Bean
	public ConcurrentMapCacheManager cacheManager(a) {
		ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
		List<String> cacheNames = this.cacheProperties.getCacheNames();
		if(! cacheNames.isEmpty()) { cacheManager.setCacheNames(cacheNames); }return this.customizerInvoker.customize(cacheManager); }}Copy the code

SimpleCacheConfiguration defines a CacheManager that uses ConcurrentMap as the local cache. CaffeineCacheConfiguration also similar, defined using Caffeine as a local cache the bean. GenericCacheConfiguration

@Configuration
@ConditionalOnBean(Cache.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class GenericCacheConfiguration {
	private final CacheManagerCustomizers customizers;
	GenericCacheConfiguration(CacheManagerCustomizers customizers) {
		this.customizers = customizers;
	}
	@Bean
	public SimpleCacheManager cacheManager(Collection<Cache> caches) {
		SimpleCacheManager cacheManager = new SimpleCacheManager();
		cacheManager.setCaches(caches);
		return this.customizers.customize(cacheManager); }}Copy the code

GenericCacheConfiguration injected for SimpleCacheManager bean type, its inheritance AbstractCacheManager use ConcurrentMap to do local cache. RedisCacheConfiguration

@Configuration
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
	private final CacheProperties cacheProperties;
	private final CacheManagerCustomizers customizerInvoker;
	private final org.springframework.data.redis.cache.RedisCacheConfiguration redisCacheConfiguration;
	RedisCacheConfiguration(CacheProperties cacheProperties,
			CacheManagerCustomizers customizerInvoker,
			ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration) {
		this.cacheProperties = cacheProperties;
		this.customizerInvoker = customizerInvoker;
		this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable();
	}
	@Bean
	public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
		RedisCacheManagerBuilder builder = RedisCacheManager
				.builder(redisConnectionFactory)
				.cacheDefaults(determineConfiguration(resourceLoader.getClassLoader()));
		List<String> cacheNames = this.cacheProperties.getCacheNames();
		if(! cacheNames.isEmpty()) { builder.initialCacheNames(new LinkedHashSet<>(cacheNames));
		}
		return this.customizerInvoker.customize(builder.build());
	}
	private org.springframework.data.redis.cache.RedisCacheConfiguration determineConfiguration( ClassLoader classLoader) {
		if (this.redisCacheConfiguration ! =null) {
			return this.redisCacheConfiguration;
		}
		Redis redisProperties = this.cacheProperties.getRedis();
		org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
				.defaultCacheConfig();
		config = config.serializeValuesWith(SerializationPair
				.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
		if(redisProperties.getTimeToLive() ! =null) {
			config = config.entryTtl(redisProperties.getTimeToLive());
		}
		if(redisProperties.getKeyPrefix() ! =null) {
			config = config.prefixKeysWith(redisProperties.getKeyPrefix());
		}
		if(! redisProperties.isCacheNullValues()) { config = config.disableCachingNullValues(); }if(! redisProperties.isUseKeyPrefix()) { config = config.disableKeyPrefix(); }returnconfig; }}Copy the code

RedisCacheConfiguration injects beans of type RedisCacheManager. This configuration takes effect only if:

  • Only the application introduces redis dependencies and defines the RedisConnectionFactory
  • No other type of CacheManager is defined
  • The spring.cache.type attribute is redis
  • Configure after RedisAutoConfiguration

The redis cache configuration is a bit more complicated. Since it relies on the RedisAutoConfiguration configuration, let’s briefly look at it:

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	public RedisTemplate<Object, Object> redisTemplate( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}
	@Bean
	@ConditionalOnMissingBean
	public StringRedisTemplate stringRedisTemplate( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		returntemplate; }}Copy the code

Redis RedisAutoConfiguration dependence, and import the LettuceConnectionConfiguration and JedisConnectionConfiguration connection configuration (analysis), defines the RedisTemp The late and StringRedisTemplate beans are used by RedisCacheManager.

5. Default Settings

Above we have analyzed the principle of the cache integral, so in the above introduction to the use of which cache is used? So what kind of cache does Springboot enable without doing any configuration? Looking back we will have a look at the CacheAutoConfiguration import configuration CacheConfigurationImportSelector:

static class CacheConfigurationImportSelector implements ImportSelector {
	@Override
	public String[] selectImports(AnnotationMetadata importingClassMetadata) {
		CacheType[] types = CacheType.values();
		String[] imports = new String[types.length];
		for (int i = 0; i < types.length; i++) {
			imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
		}
		returnimports; }}Copy the code

Get the supported cache types from the enumeration class CacheType and import them from the cache configuration Cache CacheConfigurations.

public enum CacheType {
	/** * Generic caching using 'Cache' beans from the context. */
	GENERIC,

	/** * JCache (JSR-107) backed caching. */
	JCACHE,

	/** * EhCache backed caching. */
	EHCACHE,

	/** * Hazelcast backed caching. */
	HAZELCAST,

	/** * Infinispan backed caching. */
	INFINISPAN,

	/** * Couchbase backed caching. */
	COUCHBASE,

	/** * Redis backed caching. */
	REDIS,

	/** * Caffeine backed caching. */
	CAFFEINE,

	/** * Simple in-memory caching. */
	SIMPLE,

	/** * No caching. */
	NONE
}
Copy the code

There are three conditions for importing a cache configuration:

  • If you rely on third-party packages, you need to import and define related beans, such as Caffeine and Redis
  • If you customize spring.cache.type, the one that meets this condition takes precedence
  • Natural order import

Integrated the way we use and cache configuration import conditions, it is clear that the import is GenericCacheConfiguration configuration, injection is SimpleCacheManager CacheManager type, using the ConcurrentMap types of local cache, as to how Verification is not covered in this article, but you can verify for yourself if you are interested.

Four,

Spring’s cache cache programming, introduced application suite has made great, only need to start the class open caching capability can be used and define the related components, and Springboot further packaging and configuration of the cache is simplified access steps and configuration, we only need to define the cache open type and three components of the connection can be used to cache. If users have customized requirements, they only need to implement CacheManager. However, Springboot will also lose the ability to define CacheManager, which needs to be evaluated in specific scenarios. Until today in development of the Internet, especially in the electricity market, demand for caching and more demanding, some business scenarios need to be at the same time the use of local cache and remote cache (redis) to cope with the sudden flow and data issues, namely the secondary or multistage cache, Springboot does not support for this scenario gives good, also be one of the defects If there are similar requests, you can customize the extension and implementation based on Springboot cache suite and its cache configuration rules.