日常使用中spring的 @Cacheable 大家一定不陌生沟堡,基于aop機(jī)制的緩存實現(xiàn)油吭,并且可以選擇cacheManager具體提供緩存的中間件或者進(jìn)程內(nèi)緩存弱恒,類似于 @Transactional 的transactionManager 射亏,都是提供了一種多態(tài)的實現(xiàn)虫腋,抽象出上層接口莽红,實現(xiàn)則供客戶端選擇妥畏,或許這就是架構(gòu)吧,抽象的設(shè)計安吁,使用interface對外暴露可擴(kuò)展實現(xiàn)的機(jī)制醉蚁,使用abstract 整合類似實現(xiàn)。
那么我們就看看 @Cacheable提供的一種方便的機(jī)制鬼店,spel表達(dá)式取方法 參數(shù)的邏輯网棍,大家都寫過注解,但是注解邏輯需要的參數(shù)可以使用spel動態(tài)取值是不是好爽~
直接進(jìn)入主題 跟隨spring的調(diào)用鏈
直接看 @Cacheable 注解就可以了
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
// spring的別名機(jī)制妇智,這里不討論滥玷,和cacheNames作用一致
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
// 今天的主角,就從他入手
String key() default "";
// 拼接key的 抽象出來的接口
String keyGenerator() default "";
// 真正做緩存這件事的人俘陷,redis罗捎,caffine,還是其他的都可以拉盾,至于內(nèi)存還是進(jìn)程上層抽象的邏輯不關(guān)心桨菜,如果你使用caffine
//就需要自己考慮 多服務(wù)實例的一致性了
String cacheManager() default "";
String cacheResolver() default "";
// 是否可以執(zhí)行緩存的條件 也是 spel 如果返回結(jié)果true 則進(jìn)行緩存
String condition() default "";
// 如果spel 返回true 則不進(jìn)行緩存
String unless() default "";
// 是否異步執(zhí)行
boolean sync() default false;
}
接下來看 key獲取是在哪里 SpringCacheAnnotationParser#parseCacheableAnnotation 解析注解,還好就一個地方
沒有任何邏輯就是一個組裝,繼續(xù)跟蹤上述方法 SpringCacheAnnotationParser#parseCacheAnnotations 走到這里倒得,
@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;
}
也沒有太多邏輯泻红,將當(dāng)前攔截到的方法可能存在的多個 SpringCache的注解解析為集合返回,那就是支持多個SpringCache注解同時放到一個方法嘍霞掺。
@Override
@Nullable
public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
// 到上邊發(fā)現(xiàn)這里入?yún)⑹且粋€類谊路,那么可以推斷這里調(diào)用是啟動或者類加載時進(jìn)行注解解析,然后緩存注解的寫死的參數(shù)返回
DefaultCacheConfig defaultConfig = new DefaultCacheConfig(type);
return parseCacheAnnotations(defaultConfig, type);
}
//------------還有一個方法是對方法的解析也是對注解的解析返回------------------
@Override
@Nullable
public Collection<CacheOperation> parseCacheAnnotations(Method method) {
DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass());
return parseCacheAnnotations(defaultConfig, method);
}
再上邊 AnnotationCacheOperationSource#findCacheOperations ,兩個重載方法
@Override
@Nullable
protected Collection<CacheOperation> findCacheOperations(Class<?> clazz) {
return determineCacheOperations(parser -> parser.parseCacheAnnotations(clazz));
}
@Override
@Nullable
protected Collection<CacheOperation> findCacheOperations(Method method) {
return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
}
AbstractFallbackCacheOperationSource#computeCacheOperations 這里有點看不懂暫時不細(xì)做追溯菩彬,目的就是spel
AbstractFallbackCacheOperationSource#getCacheOperations 還是處理解析注解返回
如上圖直接查看第一個調(diào)用
CacheAspectSupport#execute 查看這個execute調(diào)用方是CacheInterceptor#invoke 實現(xiàn)的MethodInterceptor接口缠劝,那不用看其他的了,這里就是執(zhí)行方法攔截的地方骗灶,在這里會找到spel的動態(tài)解析噢
順便看一下攔截方法中的執(zhí)行邏輯惨恭,了解一下@Cacheable的攔截順序
@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
// 這是個一個 函數(shù)式接口作為回調(diào),這里并沒有執(zhí)行耙旦,先執(zhí)行下面execute方法 即CacheAspectSupport#execute
CacheOperationInvoker aopAllianceInvoker = () -> {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new CacheOperationInvoker.ThrowableWrapper(ex);
}
};
try {
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
}
catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
接下來看 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));
}
}
}
// 方法邏輯是后執(zhí)行噢脱羡,先進(jìn)行緩存
return invoker.invoke();
}
再看 重載方法execute
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 注解上的是否異步的字段這里決定是否異步執(zhí)行
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, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
}
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);
}
}
// -------------同步執(zhí)行緩存邏輯--------------
// --------------------下面各種注解分別執(zhí)行,可以看出來springCache注解之間的順序 緩存刪除(目標(biāo)方法invoke前)并執(zhí)行免都、緩存增
//加(猜測是先命中一次緩存锉罐,如果沒有命中先存入空數(shù)據(jù)的緩存,提前占住緩存數(shù)據(jù)绕娘,盡量減少并發(fā)緩存帶來的緩存沖洗問題)脓规、
//緩存增加(帶有數(shù)據(jù)的)、上述兩個緩存增加的真正執(zhí)行 业舍、緩存刪除(目標(biāo)方法invoke 后)并執(zhí)行
//當(dāng)然這個 是 invoke前執(zhí)行 或者后執(zhí)行 是取決于@CacheEvict 中的 beforeInvocation 配置抖拦,默認(rèn)false在后面執(zhí)行如果前面執(zhí)行unless就拿不到結(jié)果值了
// 那么spring cache 不是 延時雙刪噢,高并發(fā)可能存在數(shù)據(jù)過期數(shù)據(jù)重新灌入
// 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 LinkedList<>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
// 方法入?yún)⒔馕?用于 key condition
Object cacheValue;
// 方法結(jié)果 解析 用于 unless
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;
}
不詳細(xì)探究執(zhí)行邏輯了舷暮,來看看生成key的邏輯态罪,private 方法 generateKey
// 可以看出沒有生成key 會拋出異常,不允許null
private Object generateKey(CacheOperationContext context, @Nullable Object result) {
Object key = context.generateKey(result);
if (key == null) {
throw new IllegalArgumentException("Null key returned for cache operation (maybe you are " +
"using named params on classes without debug info?) " + context.metadata.operation);
}
if (logger.isTraceEnabled()) {
logger.trace("Computed cache key '" + key + "' for operation " + context.metadata.operation);
}
return key;
}
//------------------------繼續(xù)------------
/**
* Compute the key for the given caching operation.
*/
@Nullable
protected Object generateKey(@Nullable Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
// 終于看到 spring核心包之一 org.springframework.expression 包里的類了下面。复颈。。T.T
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
}
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
可以看到使用的 evaluator 是CacheOperationExpressionEvaluator類這個成員變量沥割,類加載時便生成耗啦,里面有生成待解析實例的方法,有解析 key condition unless 的三個方法及ConcurrentMap 成員變量緩存到內(nèi)存中机杜,將所有的Cache注解的 spel表達(dá)式緩存于此帜讲,默認(rèn) 64的大小,主要方法如下
public EvaluationContext createEvaluationContext(Collection<? extends Cache> caches,
Method method, Object[] args, Object target, Class<?> targetClass, Method targetMethod,
@Nullable Object result, @Nullable BeanFactory beanFactory) {
CacheExpressionRootObject rootObject = new CacheExpressionRootObject(
caches, method, args, target, targetClass);
CacheEvaluationContext evaluationContext = new CacheEvaluationContext(
rootObject, targetMethod, args, getParameterNameDiscoverer());
if (result == RESULT_UNAVAILABLE) {
evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
}
else if (result != NO_RESULT) {
evaluationContext.setVariable(RESULT_VARIABLE, result);
}
if (beanFactory != null) {
evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
}
return evaluationContext;
}
@Nullable
public Object key(String keyExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
return getExpression(this.keyCache, methodKey, keyExpression).getValue(evalContext);
}
public boolean condition(String conditionExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
return (Boolean.TRUE.equals(getExpression(this.conditionCache, methodKey, conditionExpression).getValue(
evalContext, Boolean.class)));
}
public boolean unless(String unlessExpression, AnnotatedElementKey methodKey, EvaluationContext evalContext) {
return (Boolean.TRUE.equals(getExpression(this.unlessCache, methodKey, unlessExpression).getValue(
evalContext, Boolean.class)));
}
然后就返回想要的key了