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

3 篇文章 0 订阅

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 = "#product.name")
    @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表达式获取了方法参数的值.

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值