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

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

问题描述

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

在方法之上使用,如下所示:

@CleverCache(key = "'orders_'.concat(#id)")公共订单 getOrder(int id) {

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

我的匹配建议如下所示:

@Around("CleverCachePointcut(cleverCache)")public Objectclevercache(ProceedingJoinPoint joinPoint, CleverCache smartCache) 抛出 Throwable {字符串表达式 = smartCache.key();//FIXME:请在此处添加工作代码:D - 通过在表达式中解释传递的 SpEL 表达式来提取密钥

我确实在那里得到了表达式,但我还没有弄清楚如何让它正确解释 SpEL 表达式.

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

知道这是如何工作的吗?我有片段的代码可在以下位置获得:

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) {

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)

My matching advice looks like the following:

@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

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.

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

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!

解决方案

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.

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.


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;
    }
}

The diff looks like this:

这篇关于使用 Spring SpEL 表达式获取 Annotation 中引用的动态参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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