Spring AOP中如何拦截元注解(annotated annotations)
How to intercept meta annotations (annotated annotations) in Spring AOP
假设我想找到所有用@Controller 注释的 classes,我会创建这个切入点:
@Pointcut("within(@org.springframework.stereotype.Controller *)")
public void controllerPointcut() {}
但是找不到那些注解了@RestController的controller。
由于 RestController 本身是用 @Controller 注释的。
关于如何在无需创建两个切入点的情况下找到使用@Controller 或@RestController 注释的 classes 的任何想法?
=====编辑====
我的真实意图如下:
父注释:
public @interface ParentAnnotation {}
子注解(用@ParentAnnotation注解):
@ParentAnnotation
public @interface ChildAnnotation {}
class答:
@ParentAnnotation
public class MyClassA {}
class乙:
@ChildAnnotation
public class MyClassB {}
现在我想通过@ParentAnnotation 找到MyClassA 和MyClassB。
找到MyClassA没问题,但是MyClassB是用@ParentAnnotation间接注解的,有没有通用的方法来处理这种情况?
这个怎么样?
@Pointcut(
"within(@org.springframework.stereotype.Controller *) || " +
"within(@org.springframework.web.bind.annotation.RestController *)" +
)
或者更短一点,但如果在 Springs 的包中有其他 classes 具有匹配的名称(我没有检查)可能太模糊了:
@Pointcut("within(@(org.springframework..*Controller) *)")
更新:至于你真正的问题,经过你的编辑我现在明白了。这也是可能的,但在语法上有点棘手。让我将您的注释重命名为 MetaAnnotation
和 MyAnnotation
,好吗?因为它们并不是真正的父子关系,所以在 OOP 意义上不涉及继承,只是嵌套。
注解:
请确保注释确实具有运行时范围。我在你的代码中没有看到这一点。
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target({ TYPE })
public @interface MetaAnnotation {}
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target({ TYPE })
@MetaAnnotation
public @interface MyAnnotation {}
Java 示例 classes:
一个class被标注了meta注解,一个被标注了标注,一个没有标注(负测试用例):
package de.scrum_master.app;
@MetaAnnotation
public class MyClassA {
public void doSomething() {}
}
package de.scrum_master.app;
@MyAnnotation
public class MyClassB {
public void doSomething() {}
}
package de.scrum_master.app;
public class MyClassC {
public void doSomething() {}
}
驱动申请:
因为我使用的是纯 Java + AspectJ 而没有 Spring,所以我使用这个小应用程序来演示结果。
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new MyClassA().doSomething();
new MyClassB().doSomething();
new MyClassC().doSomething();
}
}
看点:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MetaAnnotationInterceptor {
@Before(
"execution(* *(..)) && (" +
"within(@de.scrum_master.app.MetaAnnotation *) || " +
"within(@(@de.scrum_master.app.MetaAnnotation *) *)" +
")"
)
public void myAdvice(JoinPoint thisJoinPoint){
System.out.println(thisJoinPoint);
}
}
控制台日志:
execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())
现在,如果您想添加另一层嵌套,请添加一个新注释并用它注释以前未注释的 class:
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target({ TYPE })
@MyAnnotation
public @interface MyOtherAnnotation {}
package de.scrum_master.app;
@MyOtherAnnotation
public class MyClassC {
public void doSomething() {}
}
然后将切入点再扩展一级 nesting/recursion:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MetaAnnotationInterceptor {
@Before(
"execution(* *(..)) && (" +
"within(@de.scrum_master.app.MetaAnnotation *) || " +
"within(@(@de.scrum_master.app.MetaAnnotation *) *) || " +
"within(@(@(@de.scrum_master.app.MetaAnnotation *) *) *)" +
")"
)
public void myAdvice(JoinPoint thisJoinPoint){
System.out.println(thisJoinPoint);
}
}
控制台日志更改为:
execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())
execution(void de.scrum_master.app.MyClassC.doSomething())
P.S.: execution(* *(..))
部分仅在 AspectJ 中是必需的,以便将切入点匹配限制为方法执行,因为 AspectJ 可以拦截比 Spring AOP 更多的事件。因此,在 Spring AOP 中,您可以消除该部分以及 ... || ... || ...
部分周围的大括号。
假设我想找到所有用@Controller 注释的 classes,我会创建这个切入点:
@Pointcut("within(@org.springframework.stereotype.Controller *)")
public void controllerPointcut() {}
但是找不到那些注解了@RestController的controller。 由于 RestController 本身是用 @Controller 注释的。
关于如何在无需创建两个切入点的情况下找到使用@Controller 或@RestController 注释的 classes 的任何想法?
=====编辑==== 我的真实意图如下:
父注释:
public @interface ParentAnnotation {}
子注解(用@ParentAnnotation注解):
@ParentAnnotation
public @interface ChildAnnotation {}
class答:
@ParentAnnotation
public class MyClassA {}
class乙:
@ChildAnnotation
public class MyClassB {}
现在我想通过@ParentAnnotation 找到MyClassA 和MyClassB。 找到MyClassA没问题,但是MyClassB是用@ParentAnnotation间接注解的,有没有通用的方法来处理这种情况?
这个怎么样?
@Pointcut(
"within(@org.springframework.stereotype.Controller *) || " +
"within(@org.springframework.web.bind.annotation.RestController *)" +
)
或者更短一点,但如果在 Springs 的包中有其他 classes 具有匹配的名称(我没有检查)可能太模糊了:
@Pointcut("within(@(org.springframework..*Controller) *)")
更新:至于你真正的问题,经过你的编辑我现在明白了。这也是可能的,但在语法上有点棘手。让我将您的注释重命名为 MetaAnnotation
和 MyAnnotation
,好吗?因为它们并不是真正的父子关系,所以在 OOP 意义上不涉及继承,只是嵌套。
注解:
请确保注释确实具有运行时范围。我在你的代码中没有看到这一点。
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target({ TYPE })
public @interface MetaAnnotation {}
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target({ TYPE })
@MetaAnnotation
public @interface MyAnnotation {}
Java 示例 classes:
一个class被标注了meta注解,一个被标注了标注,一个没有标注(负测试用例):
package de.scrum_master.app;
@MetaAnnotation
public class MyClassA {
public void doSomething() {}
}
package de.scrum_master.app;
@MyAnnotation
public class MyClassB {
public void doSomething() {}
}
package de.scrum_master.app;
public class MyClassC {
public void doSomething() {}
}
驱动申请:
因为我使用的是纯 Java + AspectJ 而没有 Spring,所以我使用这个小应用程序来演示结果。
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new MyClassA().doSomething();
new MyClassB().doSomething();
new MyClassC().doSomething();
}
}
看点:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MetaAnnotationInterceptor {
@Before(
"execution(* *(..)) && (" +
"within(@de.scrum_master.app.MetaAnnotation *) || " +
"within(@(@de.scrum_master.app.MetaAnnotation *) *)" +
")"
)
public void myAdvice(JoinPoint thisJoinPoint){
System.out.println(thisJoinPoint);
}
}
控制台日志:
execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())
现在,如果您想添加另一层嵌套,请添加一个新注释并用它注释以前未注释的 class:
package de.scrum_master.app;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target({ TYPE })
@MyAnnotation
public @interface MyOtherAnnotation {}
package de.scrum_master.app;
@MyOtherAnnotation
public class MyClassC {
public void doSomething() {}
}
然后将切入点再扩展一级 nesting/recursion:
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MetaAnnotationInterceptor {
@Before(
"execution(* *(..)) && (" +
"within(@de.scrum_master.app.MetaAnnotation *) || " +
"within(@(@de.scrum_master.app.MetaAnnotation *) *) || " +
"within(@(@(@de.scrum_master.app.MetaAnnotation *) *) *)" +
")"
)
public void myAdvice(JoinPoint thisJoinPoint){
System.out.println(thisJoinPoint);
}
}
控制台日志更改为:
execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())
execution(void de.scrum_master.app.MyClassC.doSomething())
P.S.: execution(* *(..))
部分仅在 AspectJ 中是必需的,以便将切入点匹配限制为方法执行,因为 AspectJ 可以拦截比 Spring AOP 更多的事件。因此,在 Spring AOP 中,您可以消除该部分以及 ... || ... || ...
部分周围的大括号。