Pointcut for annotated methods or methods in annotated classes

Traubenfuchs picture Traubenfuchs · Mar 23, 2016 · Viewed 8.6k times · Source

I need a pointcut for methods in classes annotated with @X or methods annotated with @X. I also need the annotation object. If both the class and the method are annotated I prefer to get the method annotation as argument.

I tried the following, which creates an "inconsistent binding" warning. (Why not just set them null?)

@Around("@annotation(methodLevelX) || @within(classLevelX)")
public Object advise(ProceedingJoinPoint pjp, X methodLevelX, X classLevelX)

The following creates a "ambiguous binding of parameter(s) x across '||' in pointcut" warning. (Which does not necessarily make sense in my opinion: Why not bind the first short circuited evaluation?)

@Around("@annotation(x) || @within(x)")
public Object advise(ProceedingJoinPoint pjp, X x)

Splitting the previous attempt in two naturally results in two method calls if class and method annotations are present.

I know I could just get the method and class with reflection and my desired annotation with a pointcut like this:

@Around("@annotation(com.package.X) || @within(com.package.X)")

But I'd prefer not to.

Is there any "one pointcut, one method, one annotation argument", solution for my requirement that does not require reflection?

Answer

Nándor Előd Fekete picture Nándor Előd Fekete · Mar 24, 2016

Not quite, but almost. You will need two pointcuts, two advices, but you can delegate the work to a single method. Here's how it would look like:

@Aspect
public class AnyAspectName {

    @Pointcut("execution(@X * *.*(..))")
    void annotatedMethod() {}

    @Pointcut("execution(* (@X *).*(..))")
    void methodOfAnnotatedClass() {}

    @Around("annotatedMethod() && @annotation(methodLevelX)")
    public Object adviseAnnotatedMethods(ProceedingJoinPoint pjp, X methodLevelX) 
            throws Throwable {
        return aroundImplementation(pjp, methodLevelX);
    }

    @Around("methodOfAnnotatedClass() && !annotatedMethod() && @within(classLevelX)")
    public Object adviseMethodsOfAnnotatedClass(ProceedingJoinPoint pjp, X classLevelX) 
            throws Throwable {
        return aroundImplementation(pjp, classLevelX);
    }

    public Object aroundImplementation(ProceedingJoinPoint pjp, X annotation) 
            throws Throwable {
        return pjp.proceed();
    }

}

Note that besides splitting apart the @annotation() and @within() pointcuts, I added restrictions to the resulting pointcuts so that they aren't too broad. I suppose you want method execution join points, so I added the needed pointcut expressions that would restrict it to method execution. They are matching

  1. execution of any method annotated with @X with any return type in any class being in any package for the first advice
  2. execution of any method with any return type in any class annotated with @X for the second.

Further restricting @within(X) and @annotation(X) comes in handy, because @within(X) by itself would match

any join point where the associated code is defined in a type with an annotation of type X

which would include method-execution, method-call, constructor-execution, constructor-call, pre-initialization, static initialization, initialization, field set, field get, exception-handler, lock type join points (not all join points are valid for around advices though). Similarly, @annotation(X) by itself would mean

any join point where the subject has an annotation of type X

which could also mean most of the previously mentioned join points, depending on the target type of your annotation.