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表达式获取了方法参数的值.