Reputation: 11865
I have an annotation:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
public @interface MyAnnotation {
}
I annotate Spring MVC controllers with it:
@MyAnnotation
public class TestController { ... }
Then I add an advice which has the following:
@Pointcut("@target(MyAnnotation)")
public void annotatedWithMyAnnotation() {}
@Around("annotatedWithMyAnnotation()")
public Object executeController(ProceedingJoinPoint point) throws Throwable { ... }
Advice's method is invoked successfully.
Now I have a bunch of controllers sharing the same annotations and I want to use a stereotype annotation to group them.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MyAnnotation
... other annotations
public @interface StereotypeAnnotation {
}
And then I annotate my controllers with @StereotypeAnnotation
:
@StereotypeAnnotation
public class TestController { ... }
Controllers do not contain @MyAnnotation
directly anymore.
The problem is that in such a case @target
pointcut stops matching my controllers, and they are not advised.
Is there a way to define a pointcut that would match controllers having such indirect annotations?
Upvotes: 0
Views: 824
Reputation: 67457
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.
Upvotes: 2