中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

基于spring?@Cacheable?注解的spel表達式該如何解析執行

發布時間:2022-01-04 00:31:10 來源:億速云 閱讀:256 作者:柒染 欄目:開發技術

今天就跟大家聊聊有關基于spring @Cacheable 注解的spel表達式該如何解析執行,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。

日常使用中spring的 @Cacheable 大家一定不陌生,基于aop機制的緩存實現,并且可以選擇cacheManager具體提供緩存的中間件或者進程內緩存,類似于 @Transactional 的transactionManager ,都是提供了一種多態的實現,抽象出上層接口,實現則供客戶端選擇,或許這就是架構吧,抽象的設計,使用interface對外暴露可擴展實現的機制,使用abstract 整合類似實現。

那么我們就看看 @Cacheable提供的一種方便的機制,spel表達式取方法 參數的邏輯,大家都寫過注解,但是注解邏輯需要的參數可以使用spel動態取值是不是好爽~

直接進入主題 跟隨spring的調用鏈

直接看 @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,還是其他的都可以,至于內存還是進程上層抽象的邏輯不關心,如果你使用caffine
//就需要自己考慮 多服務實例的一致性了
    String cacheManager() default "";
    String cacheResolver() default "";
// 是否可以執行緩存的條件 也是 spel 如果返回結果true 則進行緩存
    String condition() default "";
//  如果spel 返回true 則不進行緩存
    String unless() default "";
// 是否異步執行
    boolean sync() default false;
}

接下來看 key獲取是在哪里

SpringCacheAnnotationParser#parseCacheableAnnotation 解析注解,還好就一個地方

沒有任何邏輯就是一個組裝

繼續跟蹤上述方法 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) {
// 到上邊發現這里入參是一個類,那么可以推斷這里調用是啟動或者類加載時進行注解解析,然后緩存注解的寫死的參數返回
        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 這里有點看不懂暫時不細做追溯,目的就是spel

  • AbstractFallbackCacheOperationSource#getCacheOperations 還是處理解析注解返回

基于spring?@Cacheable?注解的spel表達式該如何解析執行

調用getCacheOperations方法的地方

如上圖直接查看第一個調用

CacheAspectSupport#execute 查看這個execute調用方是CacheInterceptor#invoke 實現的MethodInterceptor接口,那不用看其他的了,這里就是執行方法攔截的地方,在這里會找到spel的動態解析噢
順便看一下攔截方法中的執行邏輯

了解一下@Cacheable的攔截順序

    @Override
    @Nullable
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
// 這是個一個 函數式接口作為回調,這里并沒有執行,先執行下面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));
                }
            }
        }
          // 方法邏輯是后執行噢,先進行緩存
        return invoker.invoke();
    }

再看 重載方法execute

    @Nullable
    private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
        // 注解上的是否異步的字段這里決定是否異步執行
        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);
            }
        }
// -------------同步執行緩存邏輯--------------
// --------------------下面各種注解分別執行,可以看出來springCache注解之間的順序 緩存刪除(目標方法invoke前)并執行、緩存增
//加(猜測是先命中一次緩存,如果沒有命中先存入空數據的緩存,提前占住緩存數據,盡量減少并發緩存帶來的緩存沖洗問題)、
//緩存增加(帶有數據的)、上述兩個緩存增加的真正執行 、緩存刪除(目標方法invoke 后)并執行
//當然這個 是 invoke前執行 或者后執行 是取決于@CacheEvict 中的 beforeInvocation 配置,默認false在后面執行如果前面執行unless就拿不到結果值了
// 那么spring cache 不是 延時雙刪噢,高并發可能存在數據過期數據重新灌入
        // 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);
        }
              // 方法入參解析 用于 key  condition
        Object cacheValue;
              // 方法結果 解析  用于 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;
    }

不詳細探究執行邏輯了,來看看生成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;
    }
//------------------------繼續------------
        /**
         * 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 成員變量緩存到內存中,將所有的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了。

看完上述內容,你們對基于spring @Cacheable 注解的spel表達式該如何解析執行有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

黔东| 七台河市| 社会| 冷水江市| 马龙县| 蒙自县| 平安县| 兴义市| 武汉市| 明水县| 师宗县| 青田县| 惠水县| 金秀| 芜湖市| 怀化市| 家居| 抚州市| 肇庆市| 航空| 巴彦县| 泗水县| 绥宁县| 临高县| 张家口市| 正蓝旗| 建昌县| 锡林浩特市| 郴州市| 大渡口区| 长汀县| 怀安县| 敦煌市| 湘潭县| 定南县| 容城县| 子洲县| 商城县| 同仁县| 信丰县| 昌都县|