Michael Andrews
Michael Andrews

Reputation: 848

Spring AspectJ, pointcut before method execution where method OR class is annotated

I'm trying to get the value of an annotation via Spring Aop AspectJ-style, where the annotation can be on the class OR the method. I tried a lot of different things, but I can only get it to work when the annotation is on the method. I'd really like to annotate ONCE on the class - but advice all the methods of the class - and access the value of the class annotation in the advice. Here's where I've ended up:

Annotation:

@Inherited
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    String value() default "";
}

Aspect:

@Aspect
public class MyAspect {
    @Pointcut("execution(@com.myco.MyAnnotation * com.myco.somepackage..*.*(..))")
    public void atExecution() { }

    @Before("atExecution() && @annotation(myAnnotation)")
    public void myAdvice(JoinPoint joinPoint, MyAnnotation myAnnotation) {
        ...
    }
}

Any thoughts? Thanks.

Upvotes: 3

Views: 5014

Answers (1)

Nándor Előd Fekete
Nándor Előd Fekete

Reputation: 7098

Short answer

While you can formulate a pointcut that will match both directly annotated methods and methods of annotated types at the same time, you cannot make a pointcut and/or advice where you bind the value of the annotation (i.e. use the annotation value in the advice code).

@Aspect
public class MyAspect {

    @Pointcut("execution(@com.myco.MyAnnotation * com.myco.somepackage..*.*(..))")
    public void atExecutionOfAnnotatedMethod() {}

    @Pointcut("execution(* (@com.myco.MyAnnotation com.myco.somepackage..*).*(..))")
    public void atExecutionOfMethodsOfAnnotatedClass() {}

    @Before("atExecutionOfAnnotatedMethod() && @annotation(myAnnotation)")
    public void myAdviceForMethodAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
        System.out.println("myAdviceForMethodAnnotation: " + myAnnotation.value());
    }

    @Before("atExecutionOfMethodsOfAnnotatedClass() && @this(myAnnotation)")
    public void myAdviceForTypeAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
        System.out.println("myAdviceForTypeAnnotation: " + myAnnotation.value());
    }

    //      /* the following pointcut will result in "inconsistent binding" errors */
    //      @Pointcut("(atExecutionOfAnnotatedMethod() && @annotation(myMethodAnnotation)) || (atExecutionOfMethodsOfAnnotatedClass() && @this(myTypeAnnotation))")
    //      public void combinedPointcut(MyAnnotation myMethodAnnotation, MyAnnotation myTypeAnnotation) {}

}

Some detail

To combine the two separate pointcuts (atExecutionOfAnnotatedMethod and atExecutionOfMethodsOfAnnotatedClass) we would have to use the OR (||) construct. Since the OR construct doesn't guarantee that either of the two annotation bindings will be present at advice execution, they will both result in a compile error (inconsistent binding). You can still handle both cases in separate advices, you may also delegate the actual advice code to a common method to avoid duplication. In that case you'll need to take care of the case where both the type and the method is annotated with @MyAnnotation because that would match both pointcuts and would result in your method doubly advised by both advices, hence your common advice handling code will execute twice.

Combining the two

If you need to combine the two cases while defending against doubly advising the target code, you need to set up a precedence between the method level annotation and the class level annotation. Based on the principle of specificity, I'd suggest to go on the route where the method level annotation takes precedence over the class level one. Your aspect would look like this:

@Aspect
public class MyCombinedAspect {

    @Pointcut("execution(@com.myco.MyAnnotation * com.myco.somepackage..*.*(..))")
    public void atExecutionOfAnnotatedMethod() {}

    @Pointcut("execution(* (@com.myco.MyAnnotation com.myco.somepackage..*).*(..))")
    public void atExecutionOfMethodsOfAnnotatedClass() {}

    @Before("atExecutionOfAnnotatedMethod() && !atExecutionOfMethodsOfAnnotatedClass() && @annotation(myAnnotation)")
    public void myAdviceForMethodAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
        handleBeforeExecution(joinPoint, myAnnotation);
    }

    @Before("atExecutionOfMethodsOfAnnotatedClass() && !atExecutionOfAnnotatedMethod() && @this(myAnnotation)")
    public void myAdviceForTypeAnnotation(JoinPoint joinPoint, MyAnnotation myAnnotation) {
        handleBeforeExecution(joinPoint, myAnnotation);
    }

    @Before("atExecutionOfMethodsOfAnnotatedClass() && atExecutionOfAnnotatedMethod() && @annotation(myMethodAnnotation)")
    public void myAdviceForDoublyAnnotated(JoinPoint joinPoint, MyAnnotation myMethodAnnotation) {
        handleBeforeExecution(joinPoint, myMethodAnnotation);
    }

    protected void handleBeforeExecution(JoinPoint joinPoint, MyAnnotation myAnnotation) {
        System.out.println(myAnnotation.value());
    }

Upvotes: 6

Related Questions