如何编写 Spring AOP 的切入点表达式以在任何深度级别编织元注释?
How to write Pointcut expression for Spring AOP to weave meta-annotation in any level of deepness?
如果我有注解:
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Loggable {
enum LogLevel {
ERROR, WARN, INFO, DEBUG, TRACE;
}
LogLevel value() default DEBUG;
}
并尝试将其用作 Spring AOP 方面连接点标记的目标标记,它在某些方面效果很好,由该复合切入点编织:
@Aspect
class LoggableAspect {
@Pointcut("within(@Loggable *)")
void aClass() {}
//@Pointcut("@annotation(Loggable)") - in straight-forward case it works too!
@Pointcut("execution(@Loggable * *(..))")
void method() {}
@Around("method() || aClass()")
Object aroundAdvice(ProceedingJoinPoint pjp) {...}
}
换句话说,当我这样写代码时,它与“直接”注释用法一样工作得很好:
@Component
public class Abc {
@Loggable
public String method(int x) {
return "string: " + x;
}
}
但是在元注释的情况下...:
@Loggable(INFO)
@Retention(RUNTIME)
public @interface Log {
}
...它不起作用,例如,在该代码中:
@Component
public class Abc {
@Log // doesn't work! :(
public String method(int x) {
return "string: " + x;
}
}
当然,我可以为 2 级深度的特殊情况编写另一个切入点:
//...
@Pointcut("execution(@(@Loggable *) * *(..))")
void metaMethod() {}
@Around("method() || metaMethod() || aClass()")
Object aroundAdvice(ProceedingJoinPoint pjp) {...}
它会工作,但我想要通用的解决方案,在任何深度级别上工作 - 3、4、5...这种风格的 AOP 可能吗?
P.S。 That issue:
execution(public * ((@Transactional *)+).*(..))
看起来是完全正确的解决方案,但不幸的是,它不适用于我的情况。我认为,这可能仅作为 AspectJ 解决方案(在 *.aj 文件中)——不适用于 Spring AOP。我说的对吗..
即使您可能不喜欢,您问题的正确答案是:Spring AOP 作为 AspectJ 的语法子集和本机 AspectJ 都没有提供专用语法来实现您想要做的事情。您实际上能做的最好的事情就是像您自己建议的那样使用嵌套语法并使用合理数量的嵌套级别。
在我的回答 中,您找到了 class 和方法级元注释的解决方案。
更新:你误会了什么
execution(public * ((@Transactional *)+).*(..))
意味着,即使您链接到的答案解释了它:
Matches the execution of any public method in a type with the Transactional
annotation, or any subtype of a type with the Transactional
annotation.
所以这个语法是关于 class 继承的,而不是关于元注释嵌套的。这意味着如果你有 @Transational class Parent
,它将匹配 class Child extends Parent
和 class Grandchild extends Child
等。语法也应该在 Spring AOP 中工作,但这并不能解决你的问题。
如果我有注解:
@Retention(RUNTIME)
@Target({TYPE, METHOD})
public @interface Loggable {
enum LogLevel {
ERROR, WARN, INFO, DEBUG, TRACE;
}
LogLevel value() default DEBUG;
}
并尝试将其用作 Spring AOP 方面连接点标记的目标标记,它在某些方面效果很好,由该复合切入点编织:
@Aspect
class LoggableAspect {
@Pointcut("within(@Loggable *)")
void aClass() {}
//@Pointcut("@annotation(Loggable)") - in straight-forward case it works too!
@Pointcut("execution(@Loggable * *(..))")
void method() {}
@Around("method() || aClass()")
Object aroundAdvice(ProceedingJoinPoint pjp) {...}
}
换句话说,当我这样写代码时,它与“直接”注释用法一样工作得很好:
@Component
public class Abc {
@Loggable
public String method(int x) {
return "string: " + x;
}
}
但是在元注释的情况下...:
@Loggable(INFO)
@Retention(RUNTIME)
public @interface Log {
}
...它不起作用,例如,在该代码中:
@Component
public class Abc {
@Log // doesn't work! :(
public String method(int x) {
return "string: " + x;
}
}
当然,我可以为 2 级深度的特殊情况编写另一个切入点:
//...
@Pointcut("execution(@(@Loggable *) * *(..))")
void metaMethod() {}
@Around("method() || metaMethod() || aClass()")
Object aroundAdvice(ProceedingJoinPoint pjp) {...}
它会工作,但我想要通用的解决方案,在任何深度级别上工作 - 3、4、5...这种风格的 AOP 可能吗?
P.S。 That issue:
execution(public * ((@Transactional *)+).*(..))
看起来是完全正确的解决方案,但不幸的是,它不适用于我的情况。我认为,这可能仅作为 AspectJ 解决方案(在 *.aj 文件中)——不适用于 Spring AOP。我说的对吗..
即使您可能不喜欢,您问题的正确答案是:Spring AOP 作为 AspectJ 的语法子集和本机 AspectJ 都没有提供专用语法来实现您想要做的事情。您实际上能做的最好的事情就是像您自己建议的那样使用嵌套语法并使用合理数量的嵌套级别。
在我的回答
更新:你误会了什么
execution(public * ((@Transactional *)+).*(..))
意味着,即使您链接到的答案解释了它:
Matches the execution of any public method in a type with the
Transactional
annotation, or any subtype of a type with theTransactional
annotation.
所以这个语法是关于 class 继承的,而不是关于元注释嵌套的。这意味着如果你有 @Transational class Parent
,它将匹配 class Child extends Parent
和 class Grandchild extends Child
等。语法也应该在 Spring AOP 中工作,但这并不能解决你的问题。