使用Spring SpEL表达式获取注释中引用的动态参数 [英] Get dynamic parameter referenced in Annotation by using Spring SpEL Expression

查看:1317
本文介绍了使用Spring SpEL表达式获取注释中引用的动态参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想做的是拥有一个看起来很像Spring提供的@Cacheable注释的注释.

What I am trying to do is to have an Annotation which looks a lot like the @Cacheable Annotation Spring is providing.

在方法顶部使用,它看起来类似于以下内容:

Used on top of a method it looks like the following:

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

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

When I use the same using Cacheable it is somehow able to interpret this SpEL-Expression and generate a key which has the value orders_1234 (for 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.

I definitly get the expression there, but I didn't yet figure out how to make it work that it is correctly interpreting the SpEL-Expression.

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

Another support syntax should be key = "T(com.example.Utils).createCacheKey(#paramOfMethodByName)" where the a static helper for creating a key is invoked.

有人知道这怎么工作吗?我可以从中获得代码片段的代码可在以下位置获得: https://github.com/eiselems/spring-redis-two-layer-cache/blob/master/src/main/java/com/marcuseisele/example/twolayercache/clevercache/ExampleAspect.java#L35

Any idea how this could work? The code where I have the snippets from is available at: https://github.com/eiselems/spring-redis-two-layer-cache/blob/master/src/main/java/com/marcuseisele/example/twolayercache/clevercache/ExampleAspect.java#L35

我们非常感谢您的帮助!

Any help is really appreciated!

推荐答案

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

It is actually quite simple to evaluate SpEL, if you have the necessary context information. Please refer to this article in order to find out how to programmatically parse SpEL.

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

As for that context information, you did not explain much about the types of methods you annotated by @CleverCache. The thing is, the pointcut intercepts all annotated methods and I do not know if each one's first parameter is an int ID. Depending on the answer to this question it is easier (just one simple case) or more difficult (you need if-else in order to just find the methods with an integer ID) to get the ID argument value from the intercepted method. Or maybe you have all sorts of expressions referencing multiple types and names of method parameters, instance variables or whatever. The solution's complexity is linked to the requirements' complexity. If you provide more info, maybe I can also provide more help.

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

Update: Having looked at your GitHub repo, I refactored your aspect for the simple case:

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表达式获取注释中引用的动态参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆