在自定义注释的 Aspect 中传递方法参数
Pass method argument in Aspect of custom annotation
我正在尝试使用类似于 org.springframework.cache.annotation.Cacheable
的东西:
自定义注释:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckEntity {
String message() default "Check entity msg";
String key() default "";
}
看点:
@Component
@Aspect
public class CheckEntityAspect {
@Before("execution(* *.*(..)) && @annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntitty checkEntity) {
System.out.println("running entity check: " + joinPoint.getSignature().getName());
}
}
服务:
@Service
@Transactional
public class EntityServiceImpl implements EntityService {
@CheckEntity(key = "#id")
public Entity getEntity(Long id) {
return new Entity(id);
}
}
我的 IDE (IntelliJ) 没有发现 key = "#id"
用法与 Cacheable
的类似用法相比有什么特别之处,后者以与纯文本不同的颜色显示。我提到 IDE 部分只是作为一个提示,以防它有帮助,看起来 IDE 提前知道这些注释,或者它只是实现了一些在我的示例中不存在的连接.
checkEntity.key 中的值是“#id”而不是预期的数字。
我尝试使用 ExpressionParser
但可能不正确。
在 checkEntity 注释中获取参数值的唯一方法是访问参数数组,这不是我想要的,因为该注释也可以用于具有多个参数的方法。
有什么想法吗?
Spring 在内部使用 ExpressionEvaluator
来评估 key
参数中的 Spring 表达式语言(参见 CacheAspectSupport)
如果您想模仿相同的行为,请查看 CacheAspectSupport 是如何做到的。这是代码片段:
private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
/**
* Compute the key for the given caching operation.
* @return the generated key, or {@code null} if none can be generated
*/
protected Object generateKey(Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey, evaluationContext);
}
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
private EvaluationContext createEvaluationContext(Object result) {
return evaluator.createEvaluationContext(
this.caches, this.metadata.method, this.args, this.target, this.metadata.targetClass, result);
}
我不知道您使用的是哪个 IDE,但它必须以不同于其他方式处理 @Cacheable
注释来突出显示参数。
我认为您可能误解了框架应该为您做什么以及您必须做什么。
SpEL 支持无法自动触发,因此您可以访问实际(解析的)值而不是表达式本身。为什么?因为有上下文,作为开发人员,您必须提供此上下文。
Intellij 中的支持也是一样的。目前,Jetbrains 开发人员跟踪使用 SpEL 的地方,并将它们标记为支持 SpEL。我们没有任何方法来证明该值是一个实际的 SpEL 表达式(毕竟这是注释类型上的原始 java.lang.String
)。
从 4.2 开始,我们提取了缓存抽象在内部使用的一些实用程序。您可能想从这些东西中受益(通常是 CachedExpressionEvaluator
和 MethodBasedEvaluationContext
)。
新的 @EventListener
正在使用这些东西,因此您有更多代码可以作为您尝试做的事情的示例:EventExpressionEvaluator
.
总之,您的自定义拦截器需要根据 #id
值执行某些操作。 code snippet 是此类处理的一个示例,它完全不依赖于缓存抽象。
您的注释可以用于具有 1 个以上参数的方法,但这并不意味着您不能使用参数数组。这是一个解决方案:
首先我们必须找到"id"参数的索引。你可以这样做:
private Integer getParameterIdx(ProceedingJoinPoint joinPoint, String paramName) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = methodSignature.getParameterNames();
for (int i = 0; i < parameterNames.length; i++) {
String parameterName = parameterNames[i];
if (paramName.equals(parameterName)) {
return i;
}
}
return -1;
}
其中 "paramName" = 你的 "id" 参数
接下来,您可以像这样从参数中获取实际的 id 值:
Integer parameterIdx = getParameterIdx(joinPoint, "id");
Long id = joinPoint.getArgs()[parameterIdx];
当然,这假设您总是将该参数命名为 "id"。一种修复可能是允许在注释上指定参数名称,例如
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckEntity {
String message() default "Check entity msg";
String key() default "";
String paramName() default "id";
}
感谢@StéphaneNicoll,我成功地创建了一个可行的解决方案的第一个版本:
看点
@Component
@Aspect
public class CheckEntityAspect {
protected final Log logger = LogFactory.getLog(getClass());
private ExpressionEvaluator<Long> evaluator = new ExpressionEvaluator<>();
@Before("execution(* *.*(..)) && @annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntity checkEntity) {
Long result = getValue(joinPoint, checkEntity.key());
logger.info("result: " + result);
System.out.println("running entity check: " + joinPoint.getSignature().getName());
}
private Long getValue(JoinPoint joinPoint, String condition) {
return getValue(joinPoint.getTarget(), joinPoint.getArgs(),
joinPoint.getTarget().getClass(),
((MethodSignature) joinPoint.getSignature()).getMethod(), condition);
}
private Long getValue(Object object, Object[] args, Class clazz, Method method, String condition) {
if (args == null) {
return null;
}
EvaluationContext evaluationContext = evaluator.createEvaluationContext(object, clazz, method, args);
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, clazz);
return evaluator.condition(condition, methodKey, evaluationContext, Long.class);
}
}
表达式求值器
public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
// shared param discoverer since it caches data internally
private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
/**
* Create the suitable {@link EvaluationContext} for the specified event handling
* on the specified method.
*/
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);
}
/**
* Specify if the condition defined by the specified expression matches.
*/
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;
}
}
使用 Spring 表达式 添加另一种更简单的方法。参考如下:
您的注释:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckEntity {
String message() default "Check entity msg";
String keyPath() default "";
}
您的服务:
@Service
@Transactional
public class EntityServiceImpl implements EntityService {
@CheckEntity(keyPath = "[0]")
public Entity getEntity(Long id) {
return new Entity(id);
}
@CheckEntity(keyPath = "[1].otherId")
public Entity methodWithMoreThanOneArguments(String message, CustomClassForExample object) {
return new Entity(object.otherId);
}
}
class CustomClassForExample {
Long otherId;
}
你的看点:
@Component
@Aspect
public class CheckEntityAspect {
@Before("execution(* *.*(..)) && @annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntitty checkEntity) {
Object[] args = joinPoint.getArgs();
ExpressionParser elParser = new SpelExpressionParser();
Expression expression = elParser.parseExpression(checkEntity.keyPath());
Long id = (Long) expression.getValue(args);
// Do whatever you want to do with this id
// This works for both the service methods provided above and can be re-used for any number of similar methods
}
}
PS:我添加这个解决方案是因为我觉得与其他答案相比,这是一种 simpler/clearner 方法,这可能对某些人有帮助。
我正在尝试使用类似于 org.springframework.cache.annotation.Cacheable
的东西:
自定义注释:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckEntity {
String message() default "Check entity msg";
String key() default "";
}
看点:
@Component
@Aspect
public class CheckEntityAspect {
@Before("execution(* *.*(..)) && @annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntitty checkEntity) {
System.out.println("running entity check: " + joinPoint.getSignature().getName());
}
}
服务:
@Service
@Transactional
public class EntityServiceImpl implements EntityService {
@CheckEntity(key = "#id")
public Entity getEntity(Long id) {
return new Entity(id);
}
}
我的 IDE (IntelliJ) 没有发现 key = "#id"
用法与 Cacheable
的类似用法相比有什么特别之处,后者以与纯文本不同的颜色显示。我提到 IDE 部分只是作为一个提示,以防它有帮助,看起来 IDE 提前知道这些注释,或者它只是实现了一些在我的示例中不存在的连接.
checkEntity.key 中的值是“#id”而不是预期的数字。
我尝试使用 ExpressionParser
但可能不正确。
在 checkEntity 注释中获取参数值的唯一方法是访问参数数组,这不是我想要的,因为该注释也可以用于具有多个参数的方法。
有什么想法吗?
Spring 在内部使用 ExpressionEvaluator
来评估 key
参数中的 Spring 表达式语言(参见 CacheAspectSupport)
如果您想模仿相同的行为,请查看 CacheAspectSupport 是如何做到的。这是代码片段:
private final ExpressionEvaluator evaluator = new ExpressionEvaluator();
/**
* Compute the key for the given caching operation.
* @return the generated key, or {@code null} if none can be generated
*/
protected Object generateKey(Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.methodCacheKey, evaluationContext);
}
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
private EvaluationContext createEvaluationContext(Object result) {
return evaluator.createEvaluationContext(
this.caches, this.metadata.method, this.args, this.target, this.metadata.targetClass, result);
}
我不知道您使用的是哪个 IDE,但它必须以不同于其他方式处理 @Cacheable
注释来突出显示参数。
我认为您可能误解了框架应该为您做什么以及您必须做什么。
SpEL 支持无法自动触发,因此您可以访问实际(解析的)值而不是表达式本身。为什么?因为有上下文,作为开发人员,您必须提供此上下文。
Intellij 中的支持也是一样的。目前,Jetbrains 开发人员跟踪使用 SpEL 的地方,并将它们标记为支持 SpEL。我们没有任何方法来证明该值是一个实际的 SpEL 表达式(毕竟这是注释类型上的原始 java.lang.String
)。
从 4.2 开始,我们提取了缓存抽象在内部使用的一些实用程序。您可能想从这些东西中受益(通常是 CachedExpressionEvaluator
和 MethodBasedEvaluationContext
)。
新的 @EventListener
正在使用这些东西,因此您有更多代码可以作为您尝试做的事情的示例:EventExpressionEvaluator
.
总之,您的自定义拦截器需要根据 #id
值执行某些操作。 code snippet 是此类处理的一个示例,它完全不依赖于缓存抽象。
您的注释可以用于具有 1 个以上参数的方法,但这并不意味着您不能使用参数数组。这是一个解决方案:
首先我们必须找到"id"参数的索引。你可以这样做:
private Integer getParameterIdx(ProceedingJoinPoint joinPoint, String paramName) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] parameterNames = methodSignature.getParameterNames();
for (int i = 0; i < parameterNames.length; i++) {
String parameterName = parameterNames[i];
if (paramName.equals(parameterName)) {
return i;
}
}
return -1;
}
其中 "paramName" = 你的 "id" 参数
接下来,您可以像这样从参数中获取实际的 id 值:
Integer parameterIdx = getParameterIdx(joinPoint, "id");
Long id = joinPoint.getArgs()[parameterIdx];
当然,这假设您总是将该参数命名为 "id"。一种修复可能是允许在注释上指定参数名称,例如
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckEntity {
String message() default "Check entity msg";
String key() default "";
String paramName() default "id";
}
感谢@StéphaneNicoll,我成功地创建了一个可行的解决方案的第一个版本:
看点
@Component
@Aspect
public class CheckEntityAspect {
protected final Log logger = LogFactory.getLog(getClass());
private ExpressionEvaluator<Long> evaluator = new ExpressionEvaluator<>();
@Before("execution(* *.*(..)) && @annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntity checkEntity) {
Long result = getValue(joinPoint, checkEntity.key());
logger.info("result: " + result);
System.out.println("running entity check: " + joinPoint.getSignature().getName());
}
private Long getValue(JoinPoint joinPoint, String condition) {
return getValue(joinPoint.getTarget(), joinPoint.getArgs(),
joinPoint.getTarget().getClass(),
((MethodSignature) joinPoint.getSignature()).getMethod(), condition);
}
private Long getValue(Object object, Object[] args, Class clazz, Method method, String condition) {
if (args == null) {
return null;
}
EvaluationContext evaluationContext = evaluator.createEvaluationContext(object, clazz, method, args);
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, clazz);
return evaluator.condition(condition, methodKey, evaluationContext, Long.class);
}
}
表达式求值器
public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
// shared param discoverer since it caches data internally
private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
/**
* Create the suitable {@link EvaluationContext} for the specified event handling
* on the specified method.
*/
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);
}
/**
* Specify if the condition defined by the specified expression matches.
*/
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;
}
}
使用 Spring 表达式 添加另一种更简单的方法。参考如下:
您的注释:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckEntity {
String message() default "Check entity msg";
String keyPath() default "";
}
您的服务:
@Service
@Transactional
public class EntityServiceImpl implements EntityService {
@CheckEntity(keyPath = "[0]")
public Entity getEntity(Long id) {
return new Entity(id);
}
@CheckEntity(keyPath = "[1].otherId")
public Entity methodWithMoreThanOneArguments(String message, CustomClassForExample object) {
return new Entity(object.otherId);
}
}
class CustomClassForExample {
Long otherId;
}
你的看点:
@Component
@Aspect
public class CheckEntityAspect {
@Before("execution(* *.*(..)) && @annotation(checkEntity)")
public void checkEntity(JoinPoint joinPoint, CheckEntitty checkEntity) {
Object[] args = joinPoint.getArgs();
ExpressionParser elParser = new SpelExpressionParser();
Expression expression = elParser.parseExpression(checkEntity.keyPath());
Long id = (Long) expression.getValue(args);
// Do whatever you want to do with this id
// This works for both the service methods provided above and can be re-used for any number of similar methods
}
}
PS:我添加这个解决方案是因为我觉得与其他答案相比,这是一种 simpler/clearner 方法,这可能对某些人有帮助。