Spring AOP中如何拦截元注解(annotated annotations)

mao*_*ojf 1 spring annotations aspectj spring-aop

假设我想找到所有用@Controller 注释的类,我会创建这个切入点:

    @Pointcut("within(@org.springframework.stereotype.Controller *)")
    public void controllerPointcut() {}
Run Code Online (Sandbox Code Playgroud)

但是找不到那些用@RestController 注释的控制器。因为 RestController 本身是用@Controller 注释的。

关于如何查找使用 @Controller 或 @RestController 注释的类而无需创建两个 Pointcuts 的任何想法?


======编辑====我在这里的真正意图如下:

父注释:

public @interface ParentAnnotation {}
Run Code Online (Sandbox Code Playgroud)

子注释(用@ParentAnnotation 注释):

@ParentAnnotation 
public @interface ChildAnnotation {}
Run Code Online (Sandbox Code Playgroud)

A类:

@ParentAnnotation 
public class MyClassA {}
Run Code Online (Sandbox Code Playgroud)

B类:

@ChildAnnotation 
public class MyClassB {}
Run Code Online (Sandbox Code Playgroud)

现在我想通过@ParentAnnotation 找到MyClassA 和MyClassB。找到MyClassA是没有问题的,但是MyClassB是用@ParentAnnotation间接注释的,有没有通用的方法来处理这种情况?

kri*_*aex 5

这个怎么样?

@Pointcut(
  "within(@org.springframework.stereotype.Controller *) || " + 
  "within(@org.springframework.web.bind.annotation.RestController *)" + 
)
Run Code Online (Sandbox Code Playgroud)

或者更短一点,但如果 Springs 的包中有其他具有匹配名称的类(我没有检查过),则可能过于模糊:

@Pointcut("within(@(org.springframework..*Controller) *)")
Run Code Online (Sandbox Code Playgroud)

更新:至于你真正的问题,我在你编辑后现在明白了。这也是可能的,但在语法上有点棘手。让我将您的注释重命名为MetaAnnotationand 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 {}
Run Code Online (Sandbox Code Playgroud)
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 {}
Run Code Online (Sandbox Code Playgroud)

Java 示例类:

一类用元注释进行注释,一类用带注释的注释,一类不带注释(否定测试用例):

package de.scrum_master.app;

@MetaAnnotation
public class MyClassA {
  public void doSomething() {}
}
Run Code Online (Sandbox Code Playgroud)
package de.scrum_master.app;

@MyAnnotation
public class MyClassB {
  public void doSomething() {}
}
Run Code Online (Sandbox Code Playgroud)
package de.scrum_master.app;

public class MyClassC {
  public void doSomething() {}
}
Run Code Online (Sandbox Code Playgroud)

驱动程序应用:

因为我使用的是没有 Spring 的纯 Java + AspectJ,所以我使用这个小应用程序来演示结果。

package de.scrum_master.app;

public class Application {
  public static void main(String[] args) {
    new MyClassA().doSomething();
    new MyClassB().doSomething();
    new MyClassC().doSomething();
  }
}
Run Code Online (Sandbox Code Playgroud)

方面:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

控制台日志:

execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())
Run Code Online (Sandbox Code Playgroud)

现在,如果您想添加另一个级别的嵌套,请添加一个新注释并用它来注释以前未注释的类:

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 {}
Run Code Online (Sandbox Code Playgroud)
package de.scrum_master.app;

@MyOtherAnnotation
public class MyClassC {
  public void doSomething() {}
}
Run Code Online (Sandbox Code Playgroud)

然后将切入点再扩展一层嵌套/递归:

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);
  }
}
Run Code Online (Sandbox Code Playgroud)

控制台日志更改为:

execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())
execution(void de.scrum_master.app.MyClassC.doSomething())
Run Code Online (Sandbox Code Playgroud)

PS:该execution(* *(..))部分仅在 AspectJ 中是必需的,以限制切入点匹配到方法执行,因为 AspectJ 可以比 Spring AOP 拦截更多的事件。因此,在 Spring AOP 中,您可以消除该部分和围绕该... || ... || ...部分的大括号。