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();// 缓存用来解析一些属性封装的对象 CacheOperationSourceadvisor.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@Nullablepublic 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() 方法,核心源码如下:

 @Nullableprotected 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() 方法,核心源码如下:

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

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

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

 @Override@Nullablepublic 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() 方法,核心源码如下:

 @Nullableprivate 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@Nullableprotected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));}@Nullableprivate 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) 源码如下:

 @Nullableprotected 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() 核心源码如下:

 @Nullableprivate Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {// cache_tag: 只有在缓存注解上面标注了 sync=true 才会进入,默认 falseif (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 methodreturn 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 foundList<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 hitcacheValue = cacheHit.get();returnValue = wrapCacheValue(method, cacheValue);}else {// cache_tag: 如果没有命中缓存,就直接执行目标方法returnValue = invokeOperation(invoker);cacheValue = unwrapReturnValue(returnValue);}// Collect any explicit @CachePutscollectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);// Process any collected put requests, either from @CachePut or a @Cacheable missfor (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() 方法,很明显是个查询方法,核心源码如下:

 @Nullableprivate Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {Object result = CacheOperationExpressionEvaluator.NO_RESULT;for (CacheOperationContext context : contexts) {if (isConditionPassing(context, result)) {// cache_tag: 生成一个 keyObject 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() 方法,源码如下:

 @Nullableprivate 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(),源码如下:

 @Nullableprotected 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 引用功能,一步步定位到最上层调用处,然后我们从这段入口代码开始分析,源码如下:

 @Nullableprotected 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: 保存所有获取到的 Cachethis.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() 方法,源码如下:

 @Overridepublic 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() 方法,核心源码如下:

 @Overrideprotected 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 {// 根据名字获取某个缓存@NullableCache 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;}@Overridepublic String getName() {return cacheName;}@Overridepublic Object getNativeCache() {return null;}@Overridepublic ValueWrapper get(Object key) {System.out.println(">>>>>>我是 MyMapCache 缓存中的 get() 方法");Object o = map.get(key);if (Objects.nonNull(o)) {return new SimpleValueWrapper(o);}return null;}@Overridepublic <T> T get(Object key, Class<T> type) {return (T)map.get(key);}@Overridepublic <T> T get(Object key, Callable<T> valueLoader) {return (T)map.get(key);}@Overridepublic void put(Object key, Object value) {System.out.println(">>>>>>我是 MyMapCache 缓存中的 put() 方法");map.put(key, value);}@Overridepublic void evict(Object key) {map.remove(key);}@Overridepublic 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();@Overridepublic 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() 方法,源码如下:

 @Overridepublic 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 缓存实例,源码如下:

@Overridepublic 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@Nullablepublic 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 registrationsynchronized (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 对多级缓存设计的支持方案。

Spring 之 @Cacheable 源码解析(下)相关推荐

  1. Spring 之 @Cacheable 源码解析(上)

    一.@EnableCaching 源码解析 当要使用 @Cacheable 注解时需要引入 @EnableCaching 注解开启缓存功能.为什么呢?现在就来看看为什么要加入 @EnableCachi ...

  2. spring aop 注入源码解析

    spring aop 注入源码解析 aop启动 AbstractApplicationContext.java @Overridepublic void refresh() throws BeansE ...

  3. spring aop 注入源码解析 1

    spring aop 注入源码解析 aop启动 AbstractApplicationContext.java @Overridepublic void refresh() throws BeansE ...

  4. spring 源码深度解析_spring源码解析之SpringIOC源码解析(下)

    前言:本篇文章接SpringIOC源码解析(上),上一篇文章介绍了使用XML的方式启动Spring,介绍了refresh 方法中的一些方法基本作用,但是并没有展开具体分析.今天就和大家一起撸一下ref ...

  5. Spring Cloud Gateway 源码解析(3) —— Predicate

    目录 RoutePredicateFactory GatewayPredicate AfterRoutePredicateFactory RoutePredicateHandlerMapping Fi ...

  6. Laravel5.2之Filesystem源码解析(下)

    2019独角兽企业重金招聘Python工程师标准>>> 说明:本文主要学习下\League\Flysystem这个Filesystem Abstract Layer,学习下这个pac ...

  7. spring 多线程 事务 源码解析(一)

    大家好,我是烤鸭: 今天分享的是spring 多线程事务源码分析. 环境: spring-jdbc 5.0.4.REALEASE 今天分享一下spring事务的方法,这一篇还没涉及到多线程. 简单说一 ...

  8. Spring Cloud Gateway 源码解析(1) —— 基础

    目录 Gateway初始化 启用Gateway GatewayClassPathWarningAutoConfiguration GatewayLoadBalancerClientAutoConfig ...

  9. Android之EventBus框架源码解析下(源码解析)

    转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! 前言 EventBus是典型的发布订阅模式,多个订阅者可以订阅某个事件,发布者通过 ...

最新文章

  1. python导入excel数据-python + Excel数据读取(更新)
  2. vue ui框架_你为什么要使用前端框架Vue?
  3. Windows访问Fedora共享文件夹
  4. POJ 3274 Gold Balanced Lineup(哈希)
  5. mysql中profile的使用
  6. Sailfish预研结果
  7. 技术开发人员需要改变性格吗?
  8. eclipse优化设置
  9. 城市内涝监测预警系统
  10. 毕业论文使用的卡方检验该如何分析?
  11. 浏览器原理 20 # Chrome开发者工具:利用网络面板做性能分析
  12. oracle wallet java_使用Oracle sqlplus Instant客户端访问Oracle Wallet
  13. Win10(家庭版)修改中文用户名为英文
  14. Android手机如何更改hosts文件
  15. 极客时间——数据结构与算法(39) 回溯算法:从电影《蝴蝶效应》中学习回溯算法的核心思想
  16. VS2017添加lib静态库文件引用
  17. 中小企业倒闭率_死亡率98%,每分钟2家企业倒闭!中小企业如何避免“死亡规律”?...
  18. MongoDB使用用户登录访问
  19. 简单的下载excel模板
  20. aix io pacing oracle,基于文件系统的IO Pacing

热门文章

  1. Go语言实现获取有道网页结果
  2. 2021Java校招笔试题答案及评分标准
  3. 【VTK+有限元后处理】符号化矢量场绘制
  4. 传奇登陆器 link.html 网页无法显示,打开登陆器后无法显示游戏各区列表的问题...
  5. M1 MacBook的Parellel Desktop(PD)使用问题记录
  6. 1 区 IF:5+ | JGG 专刊征稿:人体微生物组
  7. php图标源码,FaviconICO图标制作在线制作生成PHP开源版源码
  8. 大数据实际案例系列一
  9. 【Python图形绘制】使用turtle库实现美国队长盾牌
  10. Qt for Android获取手机序列号