Search code examples
javaaopaspectj

AspectJ - meta/nested annotation parameter binding


I have defined a meta annotation and a number of concrete annotations annotated with the meta. I am currently trying to implement an aspect that would weave methods annotated with these concrete annotations based on the meta annotation. I was able to use this pointcut to achieve the advice invocation.

"execution(@(@my.meta.Annotation *) * *(..))"

However, I would like to make use of parameter binding because the meta annotation defines a field whose value I am interested in. Unfortunatelly, I was not able to find a correct expression to achieve that.

Is it possible to use parameter binding when using a nested annotation pointcut or do I need to get the annotation from JoinPoint?


Solution

  • Annotation binding via @annotation(), @within() needs an exact class, but you want a generic solution. Therefore, you do need to fetch the annotation from the method via reflection, i.e. you need to take the route:

    joinpoint → signature → method → annotation → meta annotation

    package de.scrum_master.stackoverflow.q75834894.app;
    
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    import java.lang.annotation.Retention;
    
    @Retention(RUNTIME)
    public @interface Meta {
      String id();
    }
    
    package de.scrum_master.stackoverflow.q75834894.app;
    
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    import java.lang.annotation.Retention;
    
    @Retention(RUNTIME)
    @Meta(id = "indirect")
    public @interface Marker {}
    
    package de.scrum_master.stackoverflow.q75834894.app;
    
    public class Application {
      public static void main(String[] args) {
        Application application = new Application();
        application.notAnnotated();
        application.metaAnnotated();
        application.markerAnnotated();
      }
    
      public void notAnnotated() {}
      @Meta(id = "direct") public void metaAnnotated() {}
      @Marker public void markerAnnotated() {}
    }
    
    package de.scrum_master.stackoverflow.q75834894.aspect;
    
    import java.lang.annotation.Annotation;
    import org.aspectj.lang.reflect.MethodSignature;
    import de.scrum_master.stackoverflow.q75834894.app.Meta;
    
    public aspect MyAspect {
      before(Meta meta) : @annotation(meta) && execution(* *(..)) {
        System.out.println(thisJoinPoint);
        System.out.println("  " + meta.id());
      }
    
      before() : execution(@(@de.scrum_master.stackoverflow.q75834894.app.Meta *) * *(..)) {
        System.out.println(thisJoinPoint);
        for (Annotation annotation : ((MethodSignature) thisJoinPoint.getSignature()).getMethod().getAnnotations()) {
          Meta meta = annotation.annotationType().getAnnotation(Meta.class);
          if (meta != null)
            System.out.println("  " + meta.id());
        }
      }
    }
    

    I guess you know how how to convert the native syntax aspect into annotation-based aspect syntax, if you prefer that.

    The console log:

    execution(void de.scrum_master.stackoverflow.q75834894.app.Application.metaAnnotated())
      direct
    execution(void de.scrum_master.stackoverflow.q75834894.app.Application.markerAnnotated())
      indirect