使用Spring SpEL表达式获取注释中引用的动态参数 - java

我正在尝试做的是一个Annotation,它看起来很像Spring提供的@Cacheable Annotation。

在方法顶部使用,它看起来如下所示:

@CleverCache(key = "'orders_'.concat(#id)")
public Order getOrder(int id) {

当我通过Cacheable使用它时,它可以某种方式解释此SpEL-Expression并生成具有值orders_1234的键(对于id = 1234)

我的匹配建议如下所示:

@Around("CleverCachePointcut(cleverCache)")
public Object clevercache(ProceedingJoinPoint joinPoint, CleverCache cleverCache) throws Throwable {
    String expression = cleverCache.key();
    //FIXME: Please add working code here :D - extracting the key by interpreting the passed SpEL Expression in expression

我肯定在那里得到了表达式,但是我还没有弄清楚如何正确地表达SpEL-Expression。

另一种支持语法应为key = "T(com.example.Utils).createCacheKey(#paramOfMethodByName)",其中将调用用于创建密钥的静态助手。

知道这怎么工作吗?我的摘录来自的代码可在以下位置找到:https://github.com/eiselems/spring-redis-two-layer-cache/blob/master/src/main/java/com/marcuseisele/example/twolayercache/clevercache/ExampleAspect.java#L35

任何帮助都非常感谢!

参考方案

如果您具有必要的上下文信息,评估SpEL实际上非常简单。请参考this article,以了解如何以编程方式解析SpEL。

关于上下文信息,您没有对由@CleverCache注释的方法类型进行过多解释。关键是,切入点会拦截所有带注释的方法,并且我不知道每个人的第一个参数是否为int ID。根据此问题的答案,更容易(仅是一种简单情况)或更困难(您需要if-else才能找到具有整数ID的方法)以从拦截的方法中获取ID参数值。或者,也许您有各种各样的表达式,它们引用方法参数,实例变量等的多种类型和名称。解决方案的复杂性与需求的复杂性相关。如果您提供更多信息,也许我也可以提供更多帮助。

更新:在查看了您的GitHub存储库后,我针对以下简单情况重构了您的方面:

package com.marcuseisele.example.twolayercache.clevercache;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
@Slf4j
public class ExampleAspect {
    private static final ExpressionParser expressionParser = new SpelExpressionParser();

    private Map<String, RedisTemplate> templates;

    public ExampleAspect(Map<String, RedisTemplate> redisTemplateMap) {
        this.templates = redisTemplateMap;
    }

    @Pointcut("@annotation(cleverCache)")
    public void CleverCachePointcut(CleverCache cleverCache) {
    }

    @Around("CleverCachePointcut(cleverCache) && args(id)")
    public Object clevercache(ProceedingJoinPoint joinPoint, CleverCache cleverCache, int id) throws Throwable {
        long ttl = cleverCache.ttl();
        long grace = cleverCache.graceTtl();

        String key = cleverCache.key();
        Expression expression = expressionParser.parseExpression(key);
        EvaluationContext context = new StandardEvaluationContext();
        context.setVariable("id", id);
        String cacheKey = (String) expression.getValue(context);
        System.out.println("### Cache key: " + cacheKey);

        long start = System.currentTimeMillis();
        RedisTemplate redisTemplate = templates.get(cleverCache.redisTemplate());
        Object result;
        if (redisTemplate.hasKey(cacheKey)) {
            result = redisTemplate.opsForValue().get(cacheKey);
            log.info("Reading from cache ..." + result.toString());

            if (redisTemplate.getExpire(cacheKey, TimeUnit.MINUTES) < grace) {
                log.info("Entry is in Grace period - trying to refresh it");
                try {
                    result = joinPoint.proceed();
                    redisTemplate.opsForValue().set(cacheKey, result, grace+ttl, TimeUnit.MINUTES);
                    log.info("Fetch was successful - new value will be returned");
                } catch (Exception e) {
                    log.warn("An error occured while trying to refresh the value - extending the old one", e);
                    //TODO: think about only adding 5 minutes on top of grace, or 50% of ttl on top of grace
                    //if protected by a circuit breaker we could go REALLY low here
                    redisTemplate.opsForValue().getOperations().expire(cacheKey, grace+ttl, TimeUnit.MINUTES);
                }

            }

        } else {
            result = joinPoint.proceed();
            log.info("Giving from method ..." + result.toString());
            redisTemplate.opsForValue().set(cacheKey, result, ttl + grace, TimeUnit.MINUTES);
        }

        long executionTime = System.currentTimeMillis() - start;
        log.info("{} executed in {} ms", joinPoint.getSignature(), executionTime);
        log.info("Result: {}", result);
        return result;
    }
}

差异看起来像这样:

使用Spring SpEL表达式获取注释中引用的动态参数 - java

Spring Boot如何在POST之后返回响应 - java

我想创建一个新客户并在创建客户后返回客户编号。客户编号必须是从50000开始的自动递增的唯一编号。到目前为止,我已经成功创建了一个客户,但是我不确定应该如何生成客户编号,将其保存到数据库中,并在触发POST时将其作为成功消息显示给用户。json下面是所需的响应;{ "customerNumber": "50002", …

春天的多属性文件 - java

我在spring中加载属性文件: <context:property-placeholder location="classpath:foo.properties"/> 但是,如果我尝试在另一个上下文文件中加载另一个文件,则会出现错误。 java大神给出的解决方案 如果您需要覆盖属性,则可以执行以下操作:<context…

Spring Data Cassandra的事务管理 - java

我正在使用Spring和Cassandra作为基础数据库。曾提到过弹簧伞项目“ spring data cassandra”。与休眠不同,在这里无法找到如何管理事务。如果您中的某些人已经合并,请共享要包含的事务管理器的详细信息。 参考方案 Cassandra不支持传统(ACID)的事务。在某些特殊情况下,可以通过一些构造来实现事务原子性,例如原子批处理(请参…

如何在Wiremock中为JUNIT匹配精确的json - java

我正在使用Wiremock在Spring启动应用程序中模拟Junit的REST服务。我的问题是,我无法匹配多个匹配模式。 Junit.javaStringValuePattern pattern = WireMock.matching(".*"); givenThat(post(urlEqualTo("/softwares�…

Spring Boot:java.time.Duration的默认序列化从字符串更改为数字 - java

我们最近从Spring Boot 2.1.9升级到2.2.1,这导致我们的测试失败。调查导致结果,默认情况下java.time.Duration类型现在序列化为不同的序列。现在,我们将得到"PT15M",而不是在JSON消息中包含字符串"900.0"。 POJO定义如下所示@JsonProperty(required …