返回顶部
首页 > 资讯 > 精选 >基于spring @Cacheable 注解的spel表达式该如何解析执行
  • 678
分享到

基于spring @Cacheable 注解的spel表达式该如何解析执行

2023-06-22 07:06:56 678人浏览 泡泡鱼
摘要

今天就跟大家聊聊有关基于spring @Cacheable 注解的spel表达式该如何解析执行,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。日常使用中spring

今天就跟大家聊聊有关基于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@Documentedpublic @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;    }//------------------------继续------------                @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表达式该如何解析执行有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注编程网精选频道,感谢大家的支持。

--结束END--

本文标题: 基于spring @Cacheable 注解的spel表达式该如何解析执行

本文链接: https://lsjlt.com/news/303374.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 基于spring @Cacheable 注解的spel表达式该如何解析执行
    今天就跟大家聊聊有关基于spring @Cacheable 注解的spel表达式该如何解析执行,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。日常使用中spring...
    99+
    2023-06-22
  • 基于spring @Cacheable 注解的spel表达式解析执行逻辑
    目录直接进入主题 跟随spring的调用链直接看 @Cacheable 注解就可以了接下来看 key获取是在哪里没有任何逻辑就是一个组装了解一下@Cacheable的拦截顺序接下来看...
    99+
    2024-04-02
  • SpringAOP如何在注解上使用SPEL表达式注入对象
    目录在注解上使用SPEL表达式注入对象场景描述具体案例补充Spring属性注入方式之SPEL表达式在注解上使用SPEL表达式注入对象 场景描述 在平时开发中,我们经常通过定义一些注解...
    99+
    2024-04-02
  • 如何使用Springboot自定义注解并支持SPEL表达式
    这篇文章主要介绍了如何使用Springboot自定义注解并支持SPEL表达式,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。Springboot自定义注解,支持SPEL表达式举...
    99+
    2023-06-29
  • PHP中基于正则表达式的路由解析方法
    在基于Web的应用程序中,路由(Routing)是一个非常重要的概念。它负责将用户的请求映射到相应的处理程序或控制器上,从而实现页面的呈现和处理。在PHP中,我们可以使用正则表达式来解析路由。正则表达式是一个强大的工具,它可以用来匹配和提取...
    99+
    2023-10-21
    PHP 正则表达式 路由解析
  • 基于Zookeeper的分布式锁该如何理解
    今天就跟大家聊聊有关基于Zookeeper的分布式锁该如何理解,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。实现分布式锁目前有三种流行方案,分别为基于数据库、Redis、Zookee...
    99+
    2023-06-04
  • spring boot基于注解声明式事务配置的示例分析
    小编给大家分享一下spring boot基于注解声明式事务配置的示例分析,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!事务配置1、配置方式一1)开启spring事务管理,在spring boot启动类添加注解@Enable...
    99+
    2023-06-20
  • java定时任务cron表达式每周执行一次的坑如何解决
    今天小编给大家分享一下java定时任务cron表达式每周执行一次的坑如何解决的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。j...
    99+
    2023-07-02
  • OSS.Core基于Dapper封装(表达式解析+Emit)仓储层的构思及实现
        最近趁着不忙,在构思一个搭建一个开源的完整项目,至于原因以及整个项目框架后边文章我再说明。既然要起一个完整的项目,那么数据仓储访问就必不可少,这篇文章我主要介绍这个新项目(OSS...
    99+
    2024-04-02
  • ​PostgreSQL如何解析查询语句中的表达式列并计算得出该列的值
    这篇文章主要介绍PostgreSQL如何解析查询语句中的表达式列并计算得出该列的值,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!表达式列是指除关系定义中的系统列/定义列之外的其他投影...
    99+
    2024-04-02
  • vue基础语法中的插值表达式如何理解
    vue基础语法中的插值表达式如何理解,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。一、vscode插件介绍在我们演示插值表达式之前,我们先安装这一个VScode给我们提供的插件...
    99+
    2023-06-29
  • 如何解析中文字体在CSS中的表达方式
    这篇文章将为大家详细讲解有关如何解析中文字体在CSS中的表达方式,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。在写一个网站的样式表的时候,都会不可避免地用到...
    99+
    2024-04-02
  • 如何在SpringBoot环境下使得自定义的注解能够使用${xxx}表达式
    如何在SpringBoot环境下使得自定义的注解能够使用${xxx}表达式,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。相关依...
    99+
    2024-04-02
  • 如何使用Dreamweaver正则表达式彻底解决zencart中的Session Cookie报错、XML解析错误以及空白
    这篇文章给大家分享的是有关如何使用Dreamweaver正则表达式彻底解决zencart中的Session Cookie报错、XML解析错误以及空白的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。zencart可能...
    99+
    2023-06-08
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作