使用 Spring SpEL 表达式获取 Annotation 中引用的动态参数

eis*_*ems 5 java aop spring spring-aop spring-boot

我想要做的是拥有一个看起来很像 Spring 提供的 @Cacheable Annotation 的 Annotation。

在方法之上使用,它看起来如下所示:

@CleverCache(key = "'orders_'.concat(#id)")
public Order getOrder(int id) {
Run Code Online (Sandbox Code Playgroud)

当我使用 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
Run Code Online (Sandbox Code Playgroud)

我绝对在那里得到了表达式,但我还没有弄清楚如何让它正确解释 SpEL-Expression。

另一个支持语法应该是key = "T(com.example.Utils).createCacheKey(#paramOfMethodByName)"用于创建密钥的静态助手被调用的地方。

知道这如何工作吗?我有片段的代码可在:https : //github.com/eiselems/spring-redis-two-layer-cache/blob/master/src/main/java/com/marcusisesele/example/twolayercache/ smartcache/ExampleAspect.java#L35

任何帮助真的很感激!

kri*_*aex 6

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

至于那个上下文信息,你没有解释你注释的方法类型@CleverCache。问题是,切入点拦截了所有带注释的方法,我不知道每个人的第一个参数是否是一个intID。根据这个问题的答案,从被拦截的方法中获取 ID 参数值更容易(只是一个简单的情况)或更难(您需要 if-else 以便仅找到具有整数 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

差异如下所示:

差异截图