关于Spring Cache的介绍,参考:https://www.jianshu.com/p/51389fbaf411,本文主要讲关于注解@Cacheable
, @CachePut
, @CacheEvict
背后是怎样实现的。
1. 相关类介绍
-
1.1
Cache
,CacheManager
: 这两个接口是Spring Cache的核心接口。-
Cache
接口主要定义了cache的操作,如get, put等。 -
CacheManager
接口定义的是cache的集合,为什么会有多个Cache对象主要是每个cache可能会有自己的过期时间、Cache的元素需要被限制等原因。
-
-
1.2
CacheInterceptor
,CacheAspectSupport
,AbstractCacheInvoker
:标记Cache注解的方法被调用时的AOP相关的类。-
CacheInterceptor
继承了AOP的MethodInterceptor
,其中方法invoke(invocation)
方法相当于@Aspect
中的Around
,可以在目标方法之前和之后写一些额外的逻辑,比如从cache中查询数据,或是写数据至cache等。 -
CacheAspectSupport
:真正提供cache操作的类,被上述的CacheInterceptor
继承。 -
AbstractCacheInvoker
:抽象类,被上述的CacheAspectSupport
继续,提供读cache、写cache等操作,如方法:doGet(cache, key) , doPut(cache, key, result)等。
-
-
1.3
CacheOperation
,AnnotationCacheOperationSource
,SpringCacheAnnotationParser
:主要是为annotation做解析的。-
CacheOperation
是个抽象类,定义了cache的name\key\condition等,继承它的主要也是上述三个注解的operation,分别是:CacheableOperation
,CachePutOperation
,CacheEvictOperation
。 -
AnnotationCacheOperationSource
类主要是读取Spring Cache的三个annotation(@Cacheable
,@CachePut
,@CacheEvict
),转成CacheOperation
。 -
SpringCacheAnnotationParser
类则负责具体的annotation parse逻辑。
-
2. Annotation解析
2.1 SpringCacheAnnotationParser
类
在1.3中介绍,SpringCacheAnnotationParser
类负责具体的annotation parse逻辑。
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
@Override
@Nullable
public Collection<CacheOperation> parseCacheAnnotations(Method method) {
DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass());
return parseCacheAnnotations(defaultConfig, method);
}
@Nullable
private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
Collection<CacheOperation> ops = parseCacheAnnotations(cachingConfig, ae, false);
if (ops != null && ops.size() > 1) {
// More than one operation found -> local declarations override interface-declared ones...
Collection<CacheOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true);
if (localOps != null) {
return localOps;
}
}
return ops;
}
@Nullable
private Collection<CacheOperation> parseCacheAnnotations(
DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {
Collection<? extends Annotation> anns = (localOnly ?
AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
if (anns.isEmpty()) {
return null;
}
final Collection<CacheOperation> ops = new ArrayList<>(1);
anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
anns.stream().filter(ann -> ann instanceof CachePut).forEach(
ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
anns.stream().filter(ann -> ann instanceof Caching).forEach(
ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
return ops;
}
// 具体的parse逻辑:
private CacheableOperation parseCacheableAnnotation(
AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {
CacheableOperation.Builder builder = new CacheableOperation.Builder();
builder.setName(ae.toString());
builder.setCacheNames(cacheable.cacheNames());
builder.setCondition(cacheable.condition());
builder.setUnless(cacheable.unless());
builder.setKey(cacheable.key());
builder.setKeyGenerator(cacheable.keyGenerator());
builder.setCacheManager(cacheable.cacheManager());
builder.setCacheResolver(cacheable.cacheResolver());
builder.setSync(cacheable.sync());
defaultConfig.applyDefault(builder);
CacheableOperation op = builder.build();
validateCacheOperation(ae, op);
return op;
}
}
比如我CourseService
有个方法getById(int)上有@Cacheable
注解:
@Cacheable(cacheNames = "courses")
public Course getById(int id) {...}
Debug上述parseCacheAnnotations()
方法的结果,可以看到CourseService
上的注解,最终会被转成CacheOperation
对象,这个对象在#1.3有介绍过,主要是存放了注解的name, key, condition等信息:
【小结】SpringCacheAnnotationParser
类图:
2.2 AnnotationCacheOperationSource
类
那么是谁在调用上述#2.1的parseCacheAnnotations()
方法?
AnnotationCacheOperationSource
在构造方法中new了上述的SpringCacheAnnotationParser
,并且在findCacheOperations(method)
方法中调用了#2.1的parse方法:
public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {
public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
}
@Override
@Nullable
protected Collection<CacheOperation> findCacheOperations(Method method) {
return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
}
}
那么,findCacheOperations(method)
是谁在调用呢?
AnnotationCacheOperationSource
继续了抽象类AbstractFallbackCacheOperationSource
,在抽象类中:
public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource {
@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 {
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;
}
}
@Nullable
private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {
// Don't allow non-public methods, as configured.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class.
Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
if (opDef != null) {
return opDef;
}
// Second try is the caching operation on the target class.
opDef = findCacheOperations(specificMethod.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
if (specificMethod != method) {
// Fallback is to look at the original method.
opDef = findCacheOperations(method);
if (opDef != null) {
return opDef;
}
// Last fallback is the class of the original method.
opDef = findCacheOperations(method.getDeclaringClass());
if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
return opDef;
}
}
return null;
}
@Nullable
protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz);
@Nullable
protected abstract Collection<CacheOperation> findCacheOperations(Method method);
}
抽象类中:方法getCacheOperations(method, targetClass)
调用了 --> computeCacheOperations(method, targetClass)
,调用了抽象方法 --> findCacheOperations(method)
而方法getCacheOperations(method, targetClass)
则在第3章介绍的类中被调用。
【小结】打星的方法即是annotation parse中最终会被外部调用的方法:
image.png
3. 目标方法被执行时的逻辑
本章节围绕CacheInterceptor
相关类展开。
@Cacheable
的定义是,在执行目标方法前,先查一遍缓存,如果缓存里有,那么就返回结果,如果没有,那么执行目标方法,并将结果写入缓存。
不难想象,用切面实现比较容易,即目标方法前做一些逻辑(查缓存,返回结果),目标方法后做一些逻辑(将结果写入缓存)。
创建CacheInterceptor
由类ProxyCachingConfiguration
负责创建CacheInterceptor
,该类位于spring-context包中,这个类本身是个Configuration,最终由@EnableCaching
负责激活。
关于@EnableCache相关,具体参考:http://it.cha138.com/nginx/show-291168.html
类ProxyCachingConfiguration
具体源码,具体的解释在源码下面:
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
@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;
}
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource);
advisor.setAdvice(cacheInterceptor);
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
}
上述定义的ProxyCachingConfiguration
,主要做了3件事:
- a. 创建了我们上述第#2章的
CacheOperationSource
接口实现。 - b. 创建了
CacheInterceptor
,并将a的值set到该类中。CacheInterceptor
实现了MethodInterceptor
接口,定义了切面方法的逻辑。 - c. 定义Advisor,并定义切点(PointCut),将和上述b绑定在一起。这里定义的切点类为
CacheOperationSourcePointcut
,其中重要的方法(即每次匹配要用到的逻辑):matches()
,这里用到了第2章的annotation parse方法:
abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
@Override
public boolean matches(Method method, Class<?> targetClass) {
CacheOperationSource cas = getCacheOperationSource();
return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}
}
【小结】左边是第2章的类图,右边是本章的类图:
image.png
3.2 详解CacheInterceptor
的invoke(invocation)
方法
CacheInterceptor
类源码:
- 首先它实现了aop中的接口
MethodInterceptor
。 - 其次,继承了
CacheAspectSupport
类,该类提供的execute()方法即为具体的逻辑。
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 {
return execute(aopAllianceInvoker, target, method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
}
具体看CacheAspectSupport
的execute()方法:
- 先拿到targetClasss
- 再拿到CacheOperationSource,这个类hold了注解
@Cacheable
,@CachePut
,@CacheEvict
的定义,即:Collection<CacheOperation> operations。 - 调用第二个execute()方法。
@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)
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();
}
第二个execute()方法:
- Synchronized case略过,看常规的case。
- 当注解为
@Cacheable
时,查询cacheHit,不为空表示有缓存存在。如果为空,需要放入cache待写的list中(即:cachePutRequests
)。 - 然后就是进行判断:
- 如果
cacheHit
不为空并且不是@CachePut
操作,那么从缓存里拿到cacheValue。 - 如果
cacheHit
为空,则执行真正的目标方法。
- 如果
- 如果为
@CachePut
操作,那么也需要放入cache待写的list中(即:cachePutRequests
)。 - 读取
cachePutRequests
,执行cache写操作。 - 对
@CacheEvict
注解进行清理cache操作。
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// Special handling of synchronized invocation
if (contexts.isSynchronized()) {
// 略
}
// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// Check if we have a cached item matching the conditions
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new 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 {
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// Process any late evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
上述CacheAspectSupport
类,execute()
方法中的从缓存里拿数据,用的方法是:findCachedItem(contexts)
,具体源码:
可以看到findCachedItem(contexts)
调用了--> findInCaches(context, key)
--> 调用了doGet(cache, key)
,而doGet方法位于CacheAspectSupport的
父类AbstractCacheInvoker
中,该抽象类提供基础的doGet,doPut,doEvict方法。
@Nullable
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
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;
}
@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;
}
【小结】
image.png
【参考】
网友评论