Search code examples
javaaopaspectjspring-aop

AspectJ- combining method annotation and set field


I have a requirement where in I need to time various method calls into a time series db.

For the same, I have created 2 annotations one for the method call:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {

    String event();
    String entity();
}

and another one for a field

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Id {
   String id();
}

The reason I need the @ID annotation is that, the id field to be pushed to influx db would be known only at run time.

So, in my method, something like this would happen:

@Id
String key;

@Auditable(event="xxx",entity="yyy")
public void methodToBeIntercepted(){
   String key = <logic to generate key>;
}

The idea that I wanted to use was add an annotation advice along with a field set advice.

@After("@annotation(auditable) && (set(@<package>.ID java.lang.String sample..*.*) && args(id))")
public void pointcutMethod(Auditable auditable,String id){
}

But the flow is never entering into the pointCutMEthod. If I change the condition to || above, then it enters but it clearly suggests that only 1 condition would be true at any given point of time.

What is it that I am doing wrongly here?


Solution

  • Your analysis is correct: The advice will never trigger. It just cannot because the two pointcuts you combine are mutually exclusive: Where @Auditable is (method call or execution) is a different joinpoint from set(). What you intend to express is the following: "Intercept member variable assignment within the control flow of a method execution." I.e. you need cflow(@annotation(auditable)).

    Annotations and driver application:

    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.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Id {
      String id();
    }
    
    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;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Auditable {
      String event();
      String entity();
    }
    
    package de.scrum_master.app;
    
    public class Application {
      @Id(id = "my ID")
      String key;
    
      public static void main(String[] args) {
        Application application = new Application();
        application.methodToBeIntercepted();
      }
    
      @Auditable(event = "xxx", entity = "yyy")
      public void methodToBeIntercepted() {
        key = "I am the key";
      }
    }
    

    Aspect:

    package de.scrum_master.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    
    import de.scrum_master.app.Auditable;
    
    @Aspect
    public class MyAspect {
      @After("cflow(@annotation(auditable)) && set(@de.scrum_master.app.Id String de.scrum_master..*.*) && args(id)")
      public void pointcutMethod(JoinPoint thisJoinPoint, Auditable auditable, String id) {
        System.out.println(thisJoinPoint);
        System.out.println("  " + auditable);
        System.out.println("  " + id);
      }
    }
    

    Console log:

    set(String de.scrum_master.app.Application.key)
      @de.scrum_master.app.Auditable(event=xxx, entity=yyy)
      I am the key