Spring 之 @Cacheable 源码解析(下)

CacheInterceptor 缓存切面处理逻辑

接着上篇 Spring 之 @Cacheable 源码解析(上) 说起,代理对象已经创建成功,接着分析调用流程。那么应该从哪里入手呢?当然是去看 Advisor 增强器。因为动态代理会调用到这些切面逻辑。

我们也知道 Advisor = Advice + Pointcut, 上篇 Spring 之 @Cacheable 源码解析(上) 已经分析了 Pointcut 中的匹配器是如何进行匹配的。这里不在过多阐述,而是直接关注到的 Advice 是在 ProxyCachingConfiguration 类中引入。这个 Advice 并不是直接实现 Advice 接口,而是去实现它的子类 MethodInterceptor 接口。

@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;
	}
}

那么看看这个类里面到底做了什么事情?进入 CacheInterceptor 类,核心源码如下:

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();
		}
	}
}

继续追踪 execute() 方法,核心源码如下:

	@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();
	}

getTargetClass() 方法不用看,就是拿到目标类, 接着分析 getCacheOperationSource() 方法,核心源码如下:

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

可以看到是返回一个 CacheOperationSource 类实例,其实这个类实例在 ProxyCachingConfiguration 类中就已经通过 @Bean 注解注入,它就是 CacheOperationSource。可以简单理解这个类就是对 @Cacheable 等注解进行解析收集。

然后再看到 getCacheOperations(method,targetClass) 方法逻辑,可以看到把当前正在调用的 method 方法,和目目标类作为参数传入进去然后做处理,核心源码如下:

	@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;
		}
	}

继续进入 computeCacheOperations() 方法,核心源码如下:

	@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;
	}

从上述源码可以看出,会判断当前被调用的方法上是否有 @Cacheable 等注解修饰,若没有,继续找该方法所在类上是否有,若没有,继续找父类接口方法 ,若还没有,继续找父类接口上是否有。最后都没找到就返回 null,表示当前方法不需要被代理。直接调用目标方法走正常执行逻辑。若是找到了那就要走切面逻辑。

看到 findCacheOperations() 方法,核心源码如下:

	@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;
	}

从这里可以看出,如果从方法上找到有 @Cacheable 等注解修饰,那么就会根据不同的注解类型封装成不同的对象,比如 @Cacheable => CacheableOperation、@CacheEvict => CacheEvictOperation 等。然后将这些对象添加到集合返回出去。

最后返回调用处 getCacheOperations(method, targetClass) 源码如下:

	@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();
	}

因为 operations 不为 null、那么就会执行 execute() 方法,这里直接 new 个 CacheOperationContexts 缓存上下文,用来封装一堆的参数。这是 Spring 最喜欢干的事情,将多个参数封成一个参数,方便传递书写阅读,反正好处多多。execute() 核心源码如下:

	@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;
	}

可以看到第一行就会去执行 processCacheEvicts() 方法,但是看到该方法的第2个参数,传入的是 true,表示在方法之前调用。但是注意 @CacheEvict 注解上的默认值是 false,进入该方法内部逻辑,如下:

	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);
			}
		}
	}

很明显这里的 if 条件满足不了。因为 @CacheEvict 注解默认是 false ,processCacheEvicts() 方法传入的 beforeInvocation = true 明显不相等。除非你在 @CacheEvict(beforeInvocation=true) 才会走这段逻辑。具体这段逻辑是做什么呢?我们不妨先看看 performCacheEvict() 方法,核心源码如下:

	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());
			}
		}
	}

这里获取到所有的 Cache 缓存,但是为什么这里能够 get 出来呢?实在哪里赋值的呢?这个问题留在后面补齐。先不分析。假设获取到了所有的 Cache,比如 Redis、LocalMap 等等,然后调用 doEvict() 方法,核心源码如下:

	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);
		}
	}

很明显这里留了个钩子方法,也就是模版方法,具体 Cache 怎么保存留个子类去实现,比如 Redis、LocalMap 等等。如果不知道这些缓存如何用的可以看我另一篇文章 Spring 之 @Cacheable 缓存使用教程 不然这里可能听不懂。doEvict() 方法就是去清除缓存,每个缓存的清除方式不一样。清除缓存看对应缓存 API 即可。

看完 processCacheEvicts() 方法,接着看 findCachedItem() 方法,很明显是个查询方法,核心源码如下:

	@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;
	}

继续看到 findInCaches() 方法,源码如下:

	@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;
	}

进入 doGet(),源码如下:

	@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
		}
	}

很明显又是一个钩子方法,留给子类去实现。get() 就是去获取缓存,可以从 Redis、LocalMap 等等。然后有值就返回该值,叫做缓存命中,没有返回 null 叫做缓存不命中。

假设缓存没命中,它还会先调用 collectPutRequests() 方法,去生成一个 CachePutRequest 请求头(看到 put 肯定是要把从目标方法中获取的数据存到缓存中)然后调用目标方法获取数据。

假设缓存命中,直接返回缓存中的值。

然后接着又会执行 collectPutRequests() 方法,去准备 CachePutRequest 请求头,但是如果上述已经有了 CachePutRequest 请求头的话,这里就会直接获取到不会重新 new 一个新的。

上面的 CachePutRequest 请求头准备好后,就要看是去处理这个请求头,调用 apply() 方法进行处理,源码如下:

	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);
		}
	}

可以看到这里又是一个钩子方法,留给具体的子类去实现。put() 就是往缓存中存值。具体怎么存缓存看对应 API。

最后又会看到调用 processCacheEvicts() 方法,但是此时第二个参数为 true,和 @CacheEvicts 注解中的默认值刚好相等,所以这里这个 processCacheEvicts() 方法就会被调用,就是去把缓存中的值清除。

回过头仔细细品此段逻辑总结:

@Cacheable 注解:先去查对应缓存(Redis、LocalMap 等缓存),缓存命中直接返回,未命中,先创建 CachePutRequest 请求头,在去调用目标方法获取数据(可能从数据库中查询数据等),然后将查到的数据保存到对应缓存中,最后返回获取到的数据。

@CacheEvicts 注解:如果设置 beforeInvocation = true,表示先删除缓存,然后再调用目标方法,反之先调用目标方法,然后删除缓存。

@CachePut 注解:每次都会重新放一份数据到缓存中。

最后在说一下下面这段代码中的 context.getCaches() 是在哪个地方赋上值的,为什么在这里就直接能够 get 到 caches 值??? 源码如下:

	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());
			}
		}
	}

利用 IDEA 引用功能,一步步定位到最上层调用处,然后我们从这段入口代码开始分析,源码如下:

	@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();
	}

注意这个方法 getCacheOperationSource() 会去解析 @Cacheable 等注解的配置属性,解析到一个注解就封装名称一个 CacheOperation 对象。然后添加到集合中。这个集合中就有每个注解对应的配置信息。最后将这个集合在包装到 CacheOperationSource 对象中。反正 Spring 就是喜欢这样层层包装对象。

然后在通过 CacheOperationSource 对象获取到集合,这个集合都是解析好的 @Cacheable 等注解配置信息。最后 Spring 又把这些解析好的集合 operations 封装到 CacheOperationContexts 对象中。

进入 CacheOperationContexts 构造中,源码如下:

	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);
	}

在 CacheOperationContexts 类构造器中挨个遍历集合 operations,这里面存的都是前面解析好的 @Cacheable 等注解的配置信息。

进入 getOperationContext() 方法,源码如下:

	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);
	}

将 @Cacheable、或 @CacheEvict、或 @CachePut、或 @Caching 注解封装成单独的 CacheOperationContext 对象,反正 Spring 就喜欢这样干,喜欢把多个参数合并成一个大的上下文参数。好处多多。

然后进入 CacheOperationContext 类构造方法中,源码如下:

	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);
	}

然后可以看到在这里 caches 缓存被赋值了。那么看下具体是怎么赋值的。进入 getCaches() 方法,源码如下:

	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;
	}

继续跟踪,进入 resolveCaches() 方法,源码如下:

	@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;
	}

上面这段代码有三个地方非常重要。getCacheNames() 获取 @Cacheable 等注解上配置的 cacheNames 属性值。getCacheManager() 方法获取到某个 CacheManager 实例。然后通过这个实例拿到对应的 Cache 缓存实例。然后将这个缓存实例添加到 result 集合中返回最终赋值给 caches 成员变量。所以最终它在上面那个地方就能够直接 get 到数据。

这里重点研究下这三个方法,进入 getCacheNames() 方法,核心源码如下:

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

非常简单直接获取值即可。前面就已经解析好了 @Cacheable 等注解配置信息并封装到 CacheOperation 对象中,所以这里直接通过 context.getOperation() 就能够获取到对应注解的 CacheOperation 封装对象。从而可以从这个封装对象中获取到 cacheNames。就是这么简单。

在看看 getCacheManager() 方法,源码如下:

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

发现又是直接拿来使用,那么肯定又是在某个地方赋值初始化了。其实这个 CacheManager 需要自己来定义。因为你配置的 CacheManager 将决定你使用什么样的缓存。比如你要通过 @Bean 注解配置 RedisCacheManager 实例,那么必然 RedisCacheManager 引入的肯定是 Redis 缓存。EhCacheCacheManager 引入的必然是 EhCacheCache 缓存。这个 CacheManager 就是用户自定义配置的缓存管理类。当然也可以自定义。

然后再看看 CacheManager 中,提供了一个 getCache() 方法,可以用来获取一个缓存实例。

public interface CacheManager {
    
    

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

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

这里面的缓存实例你完全可以自定义,只需要实现 Cache 接口即可。例子如下:

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();
	}
}

最终 CacheManager 返回的就是我们自定义的缓存实例 MyMapCache。最后将这个 MyMapCache 实例添加到 result 集合最后赋值给 this.caches 成员变量。

不过实现 CacheManager 接口有个缺点,每次只能返回一个 Cache 实例,如果想要返回多个呢?怎么办,所以这里 Spring 早就想到,提前给你准备好了 AbstractCacheManager 抽象类。它有个 loadCaches() 方法,源码如下:

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();
}

在 loadCaches() 方法中可以返回非常多 Cache 实例,那么这么多实例要怎么存呢,肯定需要有映射关系,那么必然采用 Map,那么 key 就是对应的 cacheName,value 就是对应的 Cache,Spring 就是这样设计的。这对于需要做双缓存、三缓存设计就非常有帮助。具体源码可以看到 AbstractCacheManager 是 CacheManager 的扩展类,并且实现 InitializingBean 接口,那么就需要关注这个类的 afterPropertiesSet() 方法,源码如下:

	@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);
		}
	}

从上述代码中发现通过 loadCaches() 方法加载进来的 Cache 实例,都被一个个的存放到了 cacheMap 容器中。因为 Cache 类实例时多个,必然需要建立映射关系,所以存 Map 再好不过。

然后再看看在调用的过程中是怎么获取到对应的 Cache 缓存实例,源码如下:


	@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;
	}

getCacheManager() 获取的是实现了 AbstractCacheManager 的类,目前发现 Spring 有个 SimpleCacheManager 类已经实现,那么获取到的假设就是 SimpleCacheManager 管理类,然后进入 getCache() 方法,直接使用的就是父类 AbstractCacheManager 的模版方法,源码如下:

	@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;
	}

直接从 cacheMap 中获取对应 Cache 实例即可,因为上述已经创建好 cacheMap 映射。这就是 Spring 对多级缓存设计的支持方案。

猜你喜欢

转载自blog.csdn.net/qq_35971258/article/details/128729027