Spring's @Cacheable source code analysis (Part 2)

CacheInterceptor cache aspect processing logic

Let’s start with the previous article on Spring’s @Cacheable source code analysis (Part 1) . The proxy object has been successfully created, and then we will analyze the calling process. So where should you start? Of course, go see the Advisor enhancer. Because dynamic proxies will call these aspect logics.

We also know that Advisor = Advice + Pointcut. The previous article on Spring's @Cacheable source code analysis (Part 1) has analyzed how the matcher in Pointcut performs matching. I won't elaborate too much here, but the advice I'm directly concerned about is introduced in the ProxyCachingConfiguration class. This Advice does not directly implement the Advice interface, but implements its subclass MethodInterceptor interface.

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
    
    

	// cache_tag: 缓存方法增强器
	@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
			CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
    
    

		BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
		// 缓存用来解析一些属性封装的对象 CacheOperationSource
		advisor.setCacheOperationSource(cacheOperationSource);
		// 缓存拦截器执行对象
		advisor.setAdvice(cacheInterceptor);
		if (this.enableCaching != null) {
    
    
			advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
		}
		return advisor;
	}

	// cache_tag: Cache 注解解析器
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheOperationSource cacheOperationSource() {
    
    
		return new AnnotationCacheOperationSource();
	}

	// cache_tag: 缓存拦截器执行器
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
    
    
		CacheInterceptor interceptor = new CacheInterceptor();
		interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
		interceptor.setCacheOperationSource(cacheOperationSource);
		return interceptor;
	}
}

So take a look at what is done in this class? Enter the CacheInterceptor class, the core source code is as follows:

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 new CacheOperationInvoker.ThrowableWrapper(ex);
			}
		};

		Object target = invocation.getThis();
		Assert.state(target != null, "Target must not be null");
		try {
    
    
			// cache_tag: 开始执行真正的缓存拦截逻辑
			return execute(aopAllianceInvoker, target, method, invocation.getArguments());
		}
		catch (CacheOperationInvoker.ThrowableWrapper th) {
    
    
			throw th.getOriginal();
		}
	}
}

Continue to track the execute() method. The core source code is as follows:

	@Nullable
	protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
    
    

		// CacheMethodInterceptor 的父类 CacheAspectSupport 实现了接口 InitializingBean 接口,就是在这里把 initialized 设置成 true 的
		if (this.initialized) {
    
    
			Class<?> targetClass = getTargetClass(target);
			CacheOperationSource cacheOperationSource = getCacheOperationSource();
			if (cacheOperationSource != null) {
    
    
				Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
				if (!CollectionUtils.isEmpty(operations)) {
    
    
					return execute(invoker, method,
							new CacheOperationContexts(operations, method, args, target, targetClass));
				}
			}
		}

		return invoker.invoke();
	}

You don’t need to look at the getTargetClass() method, just get the target class, and then analyze the getCacheOperationSource() method. The core source code is as follows:

	@Nullable
	public CacheOperationSource getCacheOperationSource() {
    
    
		return this.cacheOperationSource;
	}

You can see that a CacheOperationSource class instance is returned. In fact, this class instance has been injected through the @Bean annotation in the ProxyCachingConfiguration class, and it is CacheOperationSource. You can simply understand that this class is to parse and collect annotations such as @Cacheable.

Then look at the getCacheOperations(method,targetClass) method logic. You can see that the method currently being called and the target class are passed in as parameters and then processed. The core source code is as follows:

	@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 {
    
    
			// cache_tag: 计算缓存注解上面的配置的值,然后封装成 CacheOperation 缓存属性对象,基本和事物的一样
			// 注意每个缓存注解对应一种不同的解析处理
			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;
		}
	}

Continue to enter the computeCacheOperations() method. The core source code is as follows:

	@Nullable
	private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {
    
    
		// Don't allow no-public methods as required.
		if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
    
    
			return null;
		}

		// The method may be on an interface, but we need attributes from the target class.
		// If the target class is null, the method will be unchanged.
		Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

		// First try is the method in the target class.
		Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
		if (opDef != null) {
    
    
			return opDef;
		}

		// cache_tag: 在 method 方法上找到缓存相关的注解封装成 CacheOperation 缓存对象属性,跟事物基本一样
		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;
	}

As can be seen from the above source code, it will be judged whether the currently called method is modified by annotations such as @Cacheable. If not, continue to find whether there is one in the class where the method is located. If not, continue to find the parent class interface method. If not, Continue to look for it on the parent class interface. In the end, if it is not found, null is returned, indicating that the current method does not need to be proxied. Directly call the target method to execute the normal execution logic. If you find it, you need to use sectional logic.

See the findCacheOperations() method, the core source code is as follows:

	@Override
	@Nullable
	protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
    
    
		return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
	}

	@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;
		}
		// cache_tag: 熟悉不能再熟悉的缓存注解 Cacheable/CacheEvict/CachePut/Caching
		// 注意每一种类型的注解解析是不太一样的哦,具体看 parseCacheableAnnotation() 解析方法
		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;
	}

It can be seen from here that if an annotation modification such as @Cacheable is found on the method, it will be encapsulated into different objects according to different annotation types, such as @Cacheable => CacheableOperation, @CacheEvict => CacheEvictOperation, etc. These objects are then added to the collection and returned.

Finally, return to the calling site. getCacheOperations(method, targetClass)The source code is as follows:

	@Nullable
	protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
    
    
		// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
		// CacheMethodInterceptor 的父类 CacheAspectSupport 实现了接口 InitializingBean 接口,就是在这里把 initialized 设置成 true 的
		if (this.initialized) {
    
    
			Class<?> targetClass = getTargetClass(target);
			CacheOperationSource cacheOperationSource = getCacheOperationSource();
			if (cacheOperationSource != null) {
    
    
				Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
				if (!CollectionUtils.isEmpty(operations)) {
    
    
					return execute(invoker, method,
							new CacheOperationContexts(operations, method, args, target, targetClass));
				}
			}
		}

		return invoker.invoke();
	}

Because operations is not null, the execute() method will be executed. Here, a new CacheOperationContexts cache context is used to encapsulate a bunch of parameters. This is what Spring likes to do the most, sealing multiple parameters into one parameter to facilitate passing, writing and reading. Anyway, there are many benefits. The core source code of execute() is as follows:

	@Nullable
	private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
    
    
		// cache_tag: 只有在缓存注解上面标注了 sync=true 才会进入,默认 false
		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, handleSynchronizedGet(invoker, key, cache));
				}
				catch (Cache.ValueRetrievalException ex) {
    
    
					// Directly propagate ThrowableWrapper from the invoker,
					// or potentially also an IllegalArgumentException etc.
					ReflectionUtils.rethrowRuntimeException(ex.getCause());
				}
			}
			else {
    
    
				// No caching required, only call the underlying method
				return invokeOperation(invoker);
			}
		}

		// cache_tag: 执行 CacheEvict 注解的作用,其实就是去回调 clear() 方法,在目标方法之前调用(一般不会这样干吧)
		processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
				CacheOperationExpressionEvaluator.NO_RESULT);

		// cache_tag: 执行 Cacheable 注解的作用,缓存命中,是否获取到了值,获取到了就叫做命中了
		Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

		// Collect puts from any @Cacheable miss, if no cached item is found
		List<CachePutRequest> cachePutRequests = new ArrayList<>();
		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 {
    
    
			// cache_tag: 如果没有命中缓存,就直接执行目标方法
			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);
		}

		// cache_tag: 执行 CacheEvict 注解的作用,其实就是去回调 clear() 方法
		processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

		return returnValue;
	}

You can see that the first line will execute the processCacheEvicts() method, but when you see the second parameter of the method, true is passed in, which means it is called before the method. But note that the default value on the @CacheEvict annotation is false. Enter the internal logic of the method, as follows:

	private void processCacheEvicts(
			Collection<CacheOperationContext> contexts, boolean beforeInvocation, @Nullable Object result) {
    
    

		for (CacheOperationContext context : contexts) {
    
    
			CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
			if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
    
    
				performCacheEvict(context, operation, result);
			}
		}
	}

Obviously the if condition here cannot be met. Because the @CacheEvict annotation is false by default, the beforeInvocation = true passed in the processCacheEvicts() method is obviously not equal. This logic will only be used if you use @CacheEvict(beforeInvocation=true). What exactly does this logic do? We might as well take a look at the performCacheEvict() method first. The core source code is as follows:

	private void performCacheEvict(
			CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {
    
    

		Object key = null;
		for (Cache cache : context.getCaches()) {
    
    
			if (operation.isCacheWide()) {
    
    
				logInvalidating(context, operation, null);
				doClear(cache, operation.isBeforeInvocation());
			}
			else {
    
    
				if (key == null) {
    
    
					key = generateKey(context, result);
				}
				logInvalidating(context, operation, key);
				doEvict(cache, key, operation.isBeforeInvocation());
			}
		}
	}

All Cache caches are obtained here, but why can they be obtained here? Where is the value assigned? This issue will be addressed later. No analysis first. Assume that all caches, such as Redis, LocalMap, etc., are obtained, and then the doEvict() method is called. The core source code is as follows:

	protected void doEvict(Cache cache, Object key, boolean immediate) {
    
    
		try {
    
    
			if (immediate) {
    
    
				cache.evictIfPresent(key);
			}
			else {
    
    
				cache.evict(key);
			}
		}
		catch (RuntimeException ex) {
    
    
			getErrorHandler().handleCacheEvictError(ex, cache, key);
		}
	}

Obviously, there is a hook method here, which is the template method. The specific method of saving Cache is implemented by subclasses, such as Redis, LocalMap, etc. If you don’t know how to use these caches, you can read my other article Spring’s @Cacheable cache usage tutorial , otherwise you may not understand it here. The doEvict() method is to clear the cache. Each cache is cleared in a different way. Just clear the cache and see the corresponding cache API.

After reading the processCacheEvicts() method, and then looking at the findCachedItem() method, it is obviously a query method. The core source code is as follows:

	@Nullable
	private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
    
    
		Object result = CacheOperationExpressionEvaluator.NO_RESULT;
		for (CacheOperationContext context : contexts) {
    
    
			if (isConditionPassing(context, result)) {
    
    
				// cache_tag: 生成一个 key
				Object key = generateKey(context, result);
				Cache.ValueWrapper cached = findInCaches(context, key);
				if (cached != null) {
    
    
					return cached;
				}
				else {
    
    
					if (logger.isTraceEnabled()) {
    
    
						logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
					}
				}
			}
		}
		return null;
	}

Continue to see the findInCaches() method, the source code is as follows:

	@Nullable
	private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
    
    
		for (Cache cache : context.getCaches()) {
    
    
			Cache.ValueWrapper wrapper = doGet(cache, key);
			if (wrapper != null) {
    
    
				if (logger.isTraceEnabled()) {
    
    
					logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
				}
				return wrapper;
			}
		}
		return null;
	}

Enter doGet(), the source code is as follows:

	@Nullable
	protected Cache.ValueWrapper doGet(Cache cache, Object key) {
    
    
		try {
    
    
			// cache_tag: 调用第三方实现获取对应的值,比如 redis、EhCacheCache 等
			return cache.get(key);
		}
		catch (RuntimeException ex) {
    
    
			getErrorHandler().handleCacheGetError(ex, cache, key);
			return null;  // If the exception is handled, return a cache miss
		}
	}

It is obviously another hook method, which is left to subclasses to implement. get() is to get the cache, which can be from Redis, LocalMap, etc. Then if there is a value, the value is returned, which is called a cache hit. If null is not returned, it is called a cache miss.

Assuming that the cache misses, it will first call the collectPutRequests() method to generate a CachePutRequest request header (seeing that put must store the data obtained from the target method into the cache) and then call the target method to obtain the data.

Assuming a cache hit, the value in the cache is returned directly.

Then the collectPutRequests() method will be executed to prepare the CachePutRequest request header. However, if there is already a CachePutRequest request header mentioned above, a new one will be obtained directly and will not be re-newed.

After the above CachePutRequest request header is prepared, it is necessary to process the request header and call the apply() method for processing. The source code is as follows:

	public void apply(@Nullable Object result) {
    
    
		if (this.context.canPutToCache(result)) {
    
    
			for (Cache cache : this.context.getCaches()) {
    
    
				doPut(cache, this.key, result);
			}
		}
	}

	protected void doPut(Cache cache, Object key, @Nullable Object result) {
    
    
		try {
    
    
			cache.put(key, result);
		}
		catch (RuntimeException ex) {
    
    
			getErrorHandler().handleCachePutError(ex, cache, key, result);
		}
	}

You can see that here is another hook method, which is left to specific subclasses to implement. put() is to store the value in the cache. For details on how to store the cache, see the corresponding API.

Finally, you will see the processCacheEvicts() method being called, but at this time the second parameter is true, which is exactly the same as the default value in the @CacheEvicts annotation, so the processCacheEvicts() method here will be called to remove the cached data. value cleared.

Go back and carefully study the logical summary of this paragraph:

@Cacheable annotation : First check the corresponding cache (cache such as Redis, LocalMap, etc.). If the cache hits, it will be returned directly. If it does not hit, first create a CachePutRequest request header, then call the target method to obtain the data (may query data from the database, etc.), and then The found data is saved in the corresponding cache, and finally the obtained data is returned.

@CacheEvicts annotation : If beforeInvocation = true is set, it means the cache is deleted first, and then the target method is called. Otherwise, the target method is called first, and then the cache is deleted.

@CachePut annotation : A new copy of data will be put into the cache each time.

Finally, let me talk about where context.getCaches() in the following code is assigned a value. Why can I get the cache value directly here? ? ? The source code is as follows:

	private void performCacheEvict(
			CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {
    
    

		Object key = null;
		for (Cache cache : context.getCaches()) {
    
    
			if (operation.isCacheWide()) {
    
    
				logInvalidating(context, operation, null);
				doClear(cache, operation.isBeforeInvocation());
			}
			else {
    
    
				if (key == null) {
    
    
					key = generateKey(context, result);
				}
				logInvalidating(context, operation, key);
				doEvict(cache, key, operation.isBeforeInvocation());
			}
		}
	}

Using the IDEA reference function, we can locate the top-level call point step by step. Then we start analyzing from this entry code. The source code is as follows:

	@Nullable
	protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
    
    
		// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
		// CacheMethodInterceptor 的父类 CacheAspectSupport 实现了接口 InitializingBean 接口,就是在这里把 initialized 设置成 true 的
		if (this.initialized) {
    
    
			Class<?> targetClass = getTargetClass(target);
			CacheOperationSource cacheOperationSource = getCacheOperationSource();
			if (cacheOperationSource != null) {
    
    
				Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
				if (!CollectionUtils.isEmpty(operations)) {
    
    
					return execute(invoker, method,
							new CacheOperationContexts(operations, method, args, target, targetClass));
				}
			}
		}

		return invoker.invoke();
	}

Note that this method getCacheOperationSource() will parse the configuration attributes of @Cacheable and other annotations. When an annotation is parsed, it will encapsulate a CacheOperation object. Then add to the collection. This collection contains configuration information corresponding to each annotation. Finally, wrap this collection into a CacheOperationSource object. Anyway, Spring just likes to wrap objects layer by layer like this.

Then the collection is obtained through the CacheOperationSource object. This collection is the parsed @Cacheable and other annotation configuration information. Finally, Spring encapsulates these parsed collection operations into the CacheOperationContexts object.

Enter the CacheOperationContexts structure, the source code is as follows:

	public CacheOperationContexts(Collection<? extends CacheOperation> 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);
	}

In the CacheOperationContexts class constructor, the collection operations are traversed one by one, which contains the configuration information of @Cacheable and other annotations that have been parsed earlier.

Enter the getOperationContext() method, the source code is as follows:

	protected CacheOperationContext getOperationContext(
			CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) {
    
    

		CacheOperationMetadata metadata = getCacheOperationMetadata(operation, method, targetClass);
		return new CacheOperationContext(metadata, args, target);
	}

Encapsulate the @Cacheable, or @CacheEvict, or @CachePut, or @Caching annotation into a separate CacheOperationContext object. Anyway, Spring likes to do this, and likes to merge multiple parameters into one large context parameter. There are many benefits.

Then enter the CacheOperationContext class construction method. The source code is as follows:

	public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {
    
    
		this.metadata = metadata;
		this.args = extractArgs(metadata.method, args);
		this.target = target;
		// cache_tag: 保存所有获取到的 Cache
		this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
		this.cacheNames = createCacheNames(this.caches);
	}

Then you can see that the caches cache is assigned here. So let’s take a look at how the value is assigned. Enter the getCaches() method, the source code is as follows:

	protected Collection<? extends Cache> getCaches(
			CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) {
    
    
		// cache_tag: 解析有哪些 Cache 比如 redis 等,就跟解析有哪些数据源一样一样的
		Collection<? extends Cache> caches = cacheResolver.resolveCaches(context);
		if (caches.isEmpty()) {
    
    
			throw new IllegalStateException("No cache could be resolved for '" +
					context.getOperation() + "' using resolver '" + cacheResolver +
					"'. At least one cache should be provided per cache operation.");
		}
		return caches;
	}

Continue tracking and enter the resolveCaches() method. The source code is as follows:

	@Override
	public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
    
    
		/**
		 * 获取到注解上指定的缓存名称,比如 @Cacheable 指定有 "myMapCache","myRedisCache" 两个缓存名称
		 */
		Collection<String> cacheNames = getCacheNames(context);
		if (cacheNames == null) {
    
    
			return Collections.emptyList();
		}
		Collection<Cache> result = new ArrayList<>(cacheNames.size());
		for (String cacheName : cacheNames) {
    
    
			Cache cache = getCacheManager().getCache(cacheName);
			if (cache == null) {
    
    
				throw new IllegalArgumentException("Cannot find cache named '" +
						cacheName + "' for " + context.getOperation());
			}
			result.add(cache);
		}
		return result;
	}

There are three places in the above code that are very important. getCacheNames() Gets the cacheNames attribute value configured on annotations such as @Cacheable. The getCacheManager() method obtains a CacheManager instance. Then get the corresponding Cache cache instance through this instance. Then add this cache instance to the results collection and return the final value assigned to the caches member variable. So in the end it can get the data directly from the place above.

Here we focus on studying these three methods and enter the getCacheNames() method. The core source code is as follows:

	@Override
	protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
    
    
		return context.getOperation().getCacheNames();
	}

It's very simple to get the value directly. The annotation configuration information such as @Cacheable has been parsed before and encapsulated into the CacheOperation object, so here you can directly obtain the CacheOperation encapsulated object of the corresponding annotation through context.getOperation(). Thus cacheNames can be obtained from this encapsulated object. It's that simple.

Let’s take a look at the getCacheManager() method. The source code is as follows:

	public CacheManager getCacheManager() {
    
    
		Assert.state(this.cacheManager != null, "No CacheManager set");
		return this.cacheManager;
	}

It is found that it is used directly, so it must be assigned and initialized somewhere. In fact, this CacheManager needs to be defined by yourself. Because the CacheManager you configure will determine what kind of cache you use. For example, if you want to configure a RedisCacheManager instance through the @Bean annotation, then the RedisCacheManager must introduce the Redis cache. EhCacheCacheManager must introduce the EhCacheCache cache. This CacheManager is a user-defined cache management class. Of course it can also be customized.

Then look at the CacheManager, which provides a getCache() method that can be used to obtain a cache instance.

public interface CacheManager {
    
    

	// 根据名字获取某个缓存
	@Nullable
	Cache getCache(String name);

	// 获取到所有缓存的名字
	Collection<String> getCacheNames();
}

You can completely customize the cache instance here, you only need to implement the Cache interface. Examples are as follows:

public class MyMapCache implements Cache {
    
    

	public static final Map<Object, Object> map = new ConcurrentHashMap<>();

	private String cacheName;

	public MyMapCache(String cacheName) {
    
    
		this.cacheName = cacheName;
	}

	@Override
	public String getName() {
    
    
		return cacheName;
	}

	@Override
	public Object getNativeCache() {
    
    
		return null;
	}

	@Override
	public ValueWrapper get(Object key) {
    
    
		System.out.println(">>>>>>我是 MyMapCache 缓存中的 get() 方法");
		Object o = map.get(key);
		if (Objects.nonNull(o)) {
    
    
			return new SimpleValueWrapper(o);
		}
		return null;
	}

	@Override
	public <T> T get(Object key, Class<T> type) {
    
    

		return (T)map.get(key);
	}

	@Override
	public <T> T get(Object key, Callable<T> valueLoader) {
    
    
		return (T)map.get(key);
	}

	@Override
	public void put(Object key, Object value) {
    
    
		System.out.println(">>>>>>我是 MyMapCache 缓存中的 put() 方法");
		map.put(key, value);
	}

	@Override
	public void evict(Object key) {
    
    
		map.remove(key);
	}

	@Override
	public void clear() {
    
    
		map.clear();
	}
}

Finally, what CacheManager returns is our customized cache instance MyMapCache. Finally, add this MyMapCache instance to the results collection and assign it to the this.caches member variable.

However, there is a disadvantage in implementing the CacheManager interface. Only one Cache instance can be returned at a time. What if you want to return multiple ones? What to do, so Spring has already thought of it and prepared the AbstractCacheManager abstract class for you in advance. It has a loadCaches() method, the source code is as follows:

public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
    
    

	// cache_tag: 封装成 Map,方便 getCache(cacheName) 操作
	private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);

	// cache_tag: 用来保存外界传进来的缓存管理名称,一般的话就只有一个,最多两个(本地缓存+redis缓存)
	private volatile Set<String> cacheNames = Collections.emptySet();
	@Override
	public void afterPropertiesSet() {
    
    
		initializeCaches();
	}

	public void initializeCaches() {
    
    
		// cache_tag: 预留给 CacheManager 管理类的方法,可以查看 SimpleCacheManager 子类
		// 这里我们就是配置的 SimpleCacheManager 缓存管理类,Cache 使用的 ConcurrentMapCache
		// 每个子类 Cache 都实现了 Cache 接口定义的 CRUD 方法
		Collection<? extends Cache> caches = loadCaches();

		// 下面就是一堆的赋值,如果 caches 是空的话,就是代码没哟配置缓存相关的东西,自然而然下面所有逻辑都不走了
		synchronized (this.cacheMap) {
    
    
			this.cacheNames = Collections.emptySet();
			this.cacheMap.clear();
			Set<String> cacheNames = new LinkedHashSet<>(caches.size());
			for (Cache cache : caches) {
    
    
				String name = cache.getName();
				this.cacheMap.put(name, decorateCache(cache));
				cacheNames.add(name);
			}
			this.cacheNames = Collections.unmodifiableSet(cacheNames);
		}
	}

	protected abstract Collection<? extends Cache> loadCaches();
}

A lot of Cache instances can be returned in the loadCaches() method. So how to store so many instances? There must be a mapping relationship, so Map must be used. Then the key is the corresponding cacheName, and the value is the corresponding Cache. This is how Spring is designed. of. This is very helpful when double cache or triple cache designs are required. From the specific source code, you can see that AbstractCacheManager is an extension class of CacheManager and implements the InitializingBean interface. Then you need to pay attention to the afterPropertiesSet() method of this class. The source code is as follows:

	@Override
	public void afterPropertiesSet() {
    
    
		initializeCaches();
	}

	public void initializeCaches() {
    
    
		// cache_tag: 预留给 CacheManager 管理类的方法,可以查看 SimpleCacheManager 子类
		// 这里我们就是配置的 SimpleCacheManager 缓存管理类,Cache 使用的 ConcurrentMapCache
		// 每个子类 Cache 都实现了 Cache 接口定义的 CRUD 方法
		Collection<? extends Cache> caches = loadCaches();

		// 下面就是一堆的赋值,如果 caches 是空的话,就是代码没哟配置缓存相关的东西,自然而然下面所有逻辑都不走了
		synchronized (this.cacheMap) {
    
    
			this.cacheNames = Collections.emptySet();
			this.cacheMap.clear();
			Set<String> cacheNames = new LinkedHashSet<>(caches.size());
			for (Cache cache : caches) {
    
    
				String name = cache.getName();
				this.cacheMap.put(name, decorateCache(cache));
				cacheNames.add(name);
			}
			this.cacheNames = Collections.unmodifiableSet(cacheNames);
		}
	}

From the above code, it is found that the Cache instances loaded through the loadCaches() method are stored in the cacheMap container one by one. Because there are multiple instances of the Cache class, a mapping relationship must be established, so it is best to store a Map.

Then take a look at how to obtain the corresponding Cache cache instance during the call process. The source code is as follows:


	@Override
	public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
    
    
		/**
		 * 获取到注解上指定的缓存名称,比如 @Cacheable 指定有 "myMapCache","myRedisCache" 两个缓存名称
		 */
		Collection<String> cacheNames = getCacheNames(context);
		if (cacheNames == null) {
    
    
			return Collections.emptyList();
		}
		Collection<Cache> result = new ArrayList<>(cacheNames.size());
		for (String cacheName : cacheNames) {
    
    
			Cache cache = getCacheManager().getCache(cacheName);
			if (cache == null) {
    
    
				throw new IllegalArgumentException("Cannot find cache named '" +
						cacheName + "' for " + context.getOperation());
			}
			result.add(cache);
		}
		return result;
	}

What getCacheManager() obtains is the class that implements AbstractCacheManager. Currently, it is found that Spring has a SimpleCacheManager class that has been implemented, so the assumption obtained is the SimpleCacheManager management class, and then enters the getCache() method, and directly uses the template method of the parent class AbstractCacheManager. The source code is as follows:

	@Override
	@Nullable
	public Cache getCache(String name) {
    
    
		// Quick check for existing cache...
		Cache cache = this.cacheMap.get(name);
		if (cache != null) {
    
    
			return cache;
		}

		// The provider may support on-demand cache creation...
		Cache missingCache = getMissingCache(name);
		if (missingCache != null) {
    
    
			// Fully synchronize now for missing cache registration
			synchronized (this.cacheMap) {
    
    
				cache = this.cacheMap.get(name);
				if (cache == null) {
    
    
					cache = decorateCache(missingCache);
					this.cacheMap.put(name, cache);
					updateCacheNames(name);
				}
			}
		}
		return cache;
	}

Just get the corresponding Cache instance directly from cacheMap, because the cacheMap mapping has been created above. This is Spring's support for multi-level cache design.

Guess you like

Origin blog.csdn.net/qq_35971258/article/details/128729027