基于spring?@Cacheable?注解的spel表達式解析執(zhí)行邏輯
日常使用中spring的 @Cacheable 大家一定不陌生,基于aop機制的緩存實現(xiàn),并且可以選擇cacheManager具體提供緩存的中間件或者進程內(nèi)緩存,類似于 @Transactional 的transactionManager ,都是提供了一種多態(tài)的實現(xiàn),抽象出上層接口,實現(xiàn)則供客戶端選擇,或許這就是架構(gòu)吧,抽象的設(shè)計,使用interface對外暴露可擴展實現(xiàn)的機制,使用abstract 整合類似實現(xiàn)。
那么我們就看看 @Cacheable提供的一種方便的機制,spel表達式取方法 參數(shù)的邏輯,大家都寫過注解,但是注解邏輯需要的參數(shù)可以使用spel動態(tài)取值是不是好爽~
直接進入主題 跟隨spring的調(diào)用鏈
直接看 @Cacheable 注解就可以了
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
? ? ? ? // spring的別名機制,這里不討論,和cacheNames作用一致
? ? @AliasFor("cacheNames")
? ? String[] value() default {};
? ? @AliasFor("value")
? ? String[] cacheNames() default {};
? ? ? ? // 今天的主角,就從他入手
? ? String key() default "";
? ? ? // 拼接key的 抽象出來的接口
? ? String keyGenerator() default "";
// 真正做緩存這件事的人,redis,caffine,還是其他的都可以,至于內(nèi)存還是進程上層抽象的邏輯不關(guān)心,如果你使用caffine
//就需要自己考慮 多服務(wù)實例的一致性了
? ? String cacheManager() default "";
? ? String cacheResolver() default "";
// 是否可以執(zhí)行緩存的條件 也是 spel 如果返回結(jié)果true 則進行緩存
? ? String condition() default "";
// ?如果spel 返回true 則不進行緩存
? ? 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;
? ? }也沒有太多邏輯,將當前攔截到的方法可能存在的多個 SpringCache的注解解析為集合返回,那就是支持多個SpringCache注解同時放到一個方法嘍。
? ? @Override
? ? @Nullable
? ? public Collection<CacheOperation> parseCacheAnnotations(Class<?> type) {
// 到上邊發(fā)現(xiàn)這里入?yún)⑹且粋€類,那么可以推斷這里調(diào)用是啟動或者類加載時進行注解解析,然后緩存注解的寫死的參數(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這里有點看不懂暫時不細做追溯,目的就是spelAbstractFallbackCacheOperationSource#getCacheOperations還是處理解析注解返回

調(diào)用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í)行噢,先進行緩存
? ? ? ? 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注解之間的順序 緩存刪除(目標方法invoke前)并執(zhí)行、緩存增
//加(猜測是先命中一次緩存,如果沒有命中先存入空數(shù)據(jù)的緩存,提前占住緩存數(shù)據(jù),盡量減少并發(fā)緩存帶來的緩存沖洗問題)、
//緩存增加(帶有數(shù)據(jù)的)、上述兩個緩存增加的真正執(zhí)行 、緩存刪除(目標方法invoke 后)并執(zhí)行
//當然這個 是 invoke前執(zhí)行 或者后執(zhí)行 是取決于@CacheEvict 中的 beforeInvocation 配置,默認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;
? ? }不詳細探究執(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表達式緩存于此,默認 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了。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- Spring中@Value使用詳解及SPEL表達式
- 詳解Spring中Spel表達式和el表達式的區(qū)別
- SpringDataElasticsearch與SpEL表達式實現(xiàn)ES動態(tài)索引
- Spring AOP如何在注解上使用SPEL表達式注入對象
- spring之SpEL表達式詳解
- 使用Springboot自定義注解,支持SPEL表達式
- Spring?Cache抽象-使用SpEL表達式解析
- Spring實戰(zhàn)之Bean定義中的SpEL表達式語言支持操作示例
- Spring組件開發(fā)模式支持SPEL表達式
- Spring spel表達式使用方法示例
- Spring中SpEL表達式的使用全解
相關(guān)文章
Java實現(xiàn)將Object轉(zhuǎn)成List的五種方法
在Java中,將一個Object轉(zhuǎn)換為List是一個常見的需求,尤其是在處理集合操作和數(shù)據(jù)轉(zhuǎn)換時,本文將詳細討論如何實現(xiàn)這一轉(zhuǎn)換,并提供一些代碼示例,需要的朋友可以參考下2025-03-03
selenium高效應(yīng)對Web頁面元素刷新的實例講解
今天小編就為大家分享一篇selenium高效應(yīng)對Web頁面元素刷新的實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-05-05
java arrayList遍歷的四種方法及Java中ArrayList類的用法
arraylist是動態(tài)數(shù)組,它具有三個好處分別是:動態(tài)的增加和減少元素 、實現(xiàn)了ICollection和IList接口、靈活的設(shè)置數(shù)組的大小,本文給大家介紹java arraylist遍歷及Java arraylist 用法,感興趣的朋友一起學(xué)習吧2015-11-11
Java使用Apache POI庫讀取Excel表格文檔的示例
POI庫是Apache提供的用于在Windows下讀寫各類微軟Office文檔的Java庫,這里我們就來看一下Java使用Apache POI庫讀取Excel表格文檔的示例:2016-06-06
SpringBoot+Mybatis-plus+shardingsphere實現(xiàn)分庫分表的方案
實現(xiàn)億級數(shù)據(jù)量分庫分表的項目是一個挑戰(zhàn)性很高的任務(wù),下面是一個基于Spring Boot的簡單實現(xiàn)方案,感興趣的朋友一起看看吧2024-03-03
Mybatis使用useGeneratedKeys獲取自增主鍵的方法
這篇文章主要給大家介紹了關(guān)于Mybatis使用useGeneratedKeys獲取自增主鍵的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習或者使用Mybatis具有一定的參考學(xué)習價值,需要的朋友們下面來一起學(xué)習學(xué)習吧2019-09-09

