2

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?

kriegaex
  • 63,017
  • 15
  • 111
  • 202
maojf
  • 37
  • 5

1 Answers1

7

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.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • thanks kriegaex, but your second solution doesn't seem to compile even after I add the missing quote. And what I am really looking forward to is a generic way suitable for any class or method annotated with annotation which itself is annotated with another specific annotation – maojf Feb 01 '18 at 03:11
  • Sorry, I wrote this on my iPad, I did not test it. I have just updated my answer not only with the missing double quote but also with the missing pair of parentheses necessary in this case because of the `..*` pattern. As for your second question, would you please edit your question and replace my placeholder with an example or better explanation of what you mean? I might understand what you want to achieve, but I am not sure. – kriegaex Feb 01 '18 at 11:09
  • I have elaborated my question, I would really appreciate if you can provide a solution, it's been bugging me for days. And thanks again, I'm using your second solution now. @kriegaex – maojf Feb 06 '18 at 02:48
  • Hats off to kriegaex !! Not only have you solved my problem but also gave me a valuable lesson on Java Annotations and AOP. Your solution works perfectly on my application. @kriegaex – maojf Feb 07 '18 at 02:45
  • 1
    This is an awesome answer. Thanks for teaching me something new. I would add only a small thing: I think it's a good idea to keep the `execution(* *(..)) && ` part even if you only use Spring AOP, because you never know in advance that you won't need to switch to AspectJ in the future, and that's the way to keep the pointcuts semantically compatible with each other. – Nándor Előd Fekete Feb 07 '18 at 18:45
  • Does not work for me if used for Spring's '@RequestMapping' as meta and e.g. '@GetMapping' as my annotation. – miracle_the_V Nov 09 '18 at 13:56
  • @miracle_the_V, maybe you want to create a new question with an [MCVE](http://stackoverflow.com/help/mcve) showing what you want to achieve, how you tried and what failed. I am quite busy, but maybe I can answer it if you point me to it. – kriegaex Nov 23 '18 at 11:32