通过AOP+Java注解+EL表达式获取方法参数的值

时间:2025-02-16 07:20:03

Java注解通过EL表达式获取方法参数值

    • 使用场景
    • 代码实现

使用场景

在使用AOP做切面时,每个方法的参数都不一致,有时候需要取某个参数值,由于AOP增强逻辑一般都是抽象通用封装的,不能指定写死取某个值,这时候可以使用注解+EL表达式来实现。

代码实现

以实现声明式分布式锁为例子

当方法需要加锁时,加上该注解。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface Lock {
    String value();

    String param() default "";

    long waitTime() default 30L;

    long leaseTime() default 100L;

    TimeUnit timeUnit() default TimeUnit.SECONDS;

    LockType type() default LockType.FAIR;
}

加锁示例

@RestController
@RequestMapping("/product")
@RequiredArgsConstructor
public class PackIndexController {

    private final UserEquitypackService userEquitypackService;

    @Lock(value = "test", param = "#")
    @PostMapping("/test")
    public String test(Product product) {
            
    }
}

处理注解,解析EL表达式从product中获取值
先实现一个EL表达式解析器

public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
    private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
    private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
    private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);


    public EvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {
        Method targetMethod = getTargetMethod(targetClass, method);
        ExpressionRootObject root = new ExpressionRootObject(object, args);
        return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
    }


    public T condition(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext, Class<T> clazz) {
        return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext, clazz);
    }

    private Method getTargetMethod(Class<?> targetClass, Method method) {
        AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
        Method targetMethod = this.targetMethodCache.get(methodKey);
        if (targetMethod == null) {
            targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
            if (targetMethod == null) {
                targetMethod = method;
            }
            this.targetMethodCache.put(methodKey, targetMethod);
        }
        return targetMethod;
    }
}

解析表达式需要的表达式根对象

public class ExpressionRootObject {
    private final Object object;
    private final Object[] args;

    public ExpressionRootObject(Object object, Object[] args) {
        this.object = object;
        this.args = args;
    }

    public Object getObject() {
        return object;
    }

    public Object[] getArgs() {
        return args;
    }
}

最后使用AspectJ的环绕通知去加锁及解析EL表达式获取值(为了便于理解,这些贴的关键代码,重点描述解析过程,而非实现分布式锁过程)

@Aspect
@Slf4j
public class RedisLockAspect  {
    private static final ExpressionEvaluator<String> EVALUATOR = new ExpressionEvaluator<>();
    private final RedisLockClient redisLockClient;

    @Around("@annotation(redisLock)")
    public Object aroundRedisLock(ProceedingJoinPoint point, Lock redisLock) {
        log.info("======================into lock=======================");
        String lockName = redisLock.value();
        Assert.hasText(lockName, "@Lock value must have length; it must not be null or empty");
        String lockParam = redisLock.param();
        String lockKey;
        if (StringUtils.isNotBlank(lockParam)) {
        	// 解析EL表达式
            String evalAsText = this.evalLockParam(point, lockParam);
            lockKey = lockName + ':' + evalAsText;
        } else {
            lockKey = lockName;
        }

        LockType lockType = redisLock.type();
        long waitTime = redisLock.waitTime();
        long leaseTime = redisLock.leaseTime();
        TimeUnit timeUnit = redisLock.timeUnit();

        return this.redisLockClient.lock(lockKey, lockType, waitTime, leaseTime, timeUnit, point::proceed);
    }

    /**
     * 解析EL表达式
     * @param point 切入点
     * @param lockParam 需要解析的EL表达式
     * @return 解析出的值
     */
    private String evalLockParam(ProceedingJoinPoint point, String lockParam) {
        MethodSignature ms = (MethodSignature) point.getSignature();
        Method method = ms.getMethod();
        Object[] args = point.getArgs();
        Object target = point.getTarget();
        Class<?> targetClass = target.getClass();
        EvaluationContext context = EVALUATOR.createEvaluationContext(target, target.getClass(), method, args);
        AnnotatedElementKey elementKey = new AnnotatedElementKey(method, targetClass);
        return EVALUATOR.condition(lockParam, elementKey, context, String.class);
    }

    public RedisLockAspect(final RedisLockClient redisLockClient) {
        this.redisLockClient = redisLockClient;
    }
}

用postman调/product/test这个接口,传递参数为

{
	name:"小米手机"
}

此时由于加了@lock注解,所以会被RedisLockAspect拦截处理
代码中最终lockKey 的值为:
test:小米手机

自此就成功通过AOP+Java注解+EL表达式获取了方法参数的值.