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