How to intercept meta annotations (annotated annotations) in Spring AOP

末鹿安然 提交于 2019-12-21 06:36:07

问题


Suppose I want to find all classes annotated with @Controller, I would create this pointcut:

    @Pointcut("within(@org.springframework.stereotype.Controller *)")
    public void controllerPointcut() {}

But those controllers annotated with @RestController can not be found. Since RestController itself is annoatated with @Controller.

Any idea on how to find classes annotated either with @Controller or @RestController without having to create two Pointcuts ?


===== edit ==== My real intention here is as follows:

parent annotation:

public @interface ParentAnnotation {}

child annotation (annotated with @ParentAnnotation):

@ParentAnnotation 
public @interface ChildAnnotation {}

class A:

@ParentAnnotation 
public class MyClassA {}

class B:

@ChildAnnotation 
public class MyClassB {}

Now I want to find both MyClassA and MyClassB through @ParentAnnotation. There's no question for finding MyClassA, but MyClassB is indirectly annotated with @ParentAnnotation, is there a generic way to deal such situation?


回答1:


How about this?

@Pointcut(
  "within(@org.springframework.stereotype.Controller *) || " + 
  "within(@org.springframework.web.bind.annotation.RestController *)" + 
)

Or a little shorter, but maybe too fuzzy if there are other classes with matching names in Springs' packages (I have not checked):

@Pointcut("within(@(org.springframework..*Controller) *)")

Update: As for your real question, I understand it now after your edit. This is also possible, but kind of tricky syntactically. Let me rename your annotations into MetaAnnotation and MyAnnotation, okay? Because they are not really parent and child of each other, there is no inheritance involved in the the OOP sense, just nesting.

Annotations:

Please make sure that the annotations have indeed runtime scope. I did not see that in your code.

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 sample classes:

One class is annotated with the meta annotation, one with the annotated annotation and one with no annotation (negative test case):

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() {}
}

Driver application:

Because I am using pure Java + AspectJ without Spring, I am using this little application in order to demonstrate the result.

package de.scrum_master.app;

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

Aspect:

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);
  }
}

Console log:

execution(void de.scrum_master.app.MyClassA.doSomething())
execution(void de.scrum_master.app.MyClassB.doSomething())

Now if you want to add yet another level of nesting, add a new annotation and annotate the formerly unannotated class with it:

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() {}
}

Then extend the pointcut by one more level of 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);
  }
}

The console log changes to:

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.: The execution(* *(..)) part is only necessary in AspectJ in order to limit pointcut matching to method executions because AspectJ can intercept more events than Spring AOP. So in Spring AOP you can eliminate that part and the braces surrounding the ... || ... || ... part.



来源:https://stackoverflow.com/questions/48541144/how-to-intercept-meta-annotations-annotated-annotations-in-spring-aop

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!