I recreated the situation with pure AspectJ because I do not like Spring AOP so much. This is why I added an extra execution(* *(..)) && in front of the advice's pointcut in order to avoid matching other joinpoints unavailable in Spring AOP, such as call(). You can remove it in Spring AOP if you like.
Okay, let's create this situation as you described it:
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
public @interface MyAnnotation {}
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@MyAnnotation
public @interface StereotypeAnnotation {}
package de.scrum_master.app;
@MyAnnotation
public class TestController {
public void doSomething() {
System.out.println("Doing something");
}
}
package de.scrum_master.app;
@StereotypeAnnotation
public class AnotherController {
public void doSomething() {
System.out.println("Doing yet another something");
}
}
This is our pure Java driver application (no Spring):
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new TestController().doSomething();
new AnotherController().doSomething();
}
}
And this is the aspect:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class MetaAnnotationAspect {
@Pointcut(
"@target(de.scrum_master.app.MyAnnotation) || " +
"@target(de.scrum_master.app.StereotypeAnnotation)"
)
public void solutionA() {}
@Around("execution(* *(..)) && solutionA()")
public Object executeController(ProceedingJoinPoint point) throws Throwable {
System.out.println(point);
return point.proceed();
}
}
The log output would be:
execution(void de.scrum_master.app.TestController.doSomething())
Doing something
execution(void de.scrum_master.app.AnotherController.doSomething())
Doing yet another something
So far, so good. But what if we add another level of nesting?
package de.scrum_master.app;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@StereotypeAnnotation
public @interface SubStereotypeAnnotation {}
package de.scrum_master.app;
@SubStereotypeAnnotation
public class YetAnotherController {
public void doSomething() {
System.out.println("Doing another something");
}
}
package de.scrum_master.app;
public class Application {
public static void main(String[] args) {
new TestController().doSomething();
new AnotherController().doSomething();
new YetAnotherController().doSomething();
}
}
Then the pointcut would not match the nested meta/stereotype annotation anymore:
execution(void de.scrum_master.app.TestController.doSomething())
Doing something
execution(void de.scrum_master.app.AnotherController.doSomething())
Doing yet another something
Doing another something
We would have to explicitly add || @target(de.scrum_master.app.StereotypeAnnotation) to the pointcut, i.e. we would have to know all annotation class names in the hierarchy. There is a way to overcome this using a special syntax for the within() pointcut designator, see also my other answer here:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class MetaAnnotationAspect {
@Pointcut(
"within(@de.scrum_master.app.MyAnnotation *) || " +
"within(@(@de.scrum_master.app.MyAnnotation *) *) || " +
"within(@(@(@de.scrum_master.app.MyAnnotation *) *) *)"
)
public void solutionB() {}
@Around("execution(* *(..)) && solutionB()")
public Object executeController(ProceedingJoinPoint point) throws Throwable {
System.out.println(point);
return point.proceed();
}
}
The console log changes to:
execution(void de.scrum_master.app.TestController.doSomething())
Doing something
execution(void de.scrum_master.app.AnotherController.doSomething())
Doing yet another something
execution(void de.scrum_master.app.YetAnotherController.doSomething())
Doing another something
See? We only need to know one annotation class, namely MyAnnotation, in order to cover two nesting levels of meta annotations. Adding more levels would be straightforward. I admit that this kind of annotation nesting seems pretty much contrived, I just wanted to explain to you which options you have.