20

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?

cb4
  • 6,689
  • 7
  • 45
  • 57
ASA
  • 1,911
  • 3
  • 20
  • 37

1 Answers1

33

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.

Nándor Előd Fekete
  • 6,988
  • 1
  • 22
  • 47
  • Thank you for your answer, I will try your idea out. I am fine with @annotation(x) catching as much as it can, since I would only every consciously put my annotation on methods, if not on a type. – ASA Mar 24 '16 at 08:59
  • Yesterday I discovered another solution: It works with two advices as well: One with simply "@annotation(x)" and the second "@within(x) && !@annotation(at.X) && !execution(*.new(..))". – ASA Mar 24 '16 at 08:59
  • `@annotation(x)` will still match 'method-call' and 'method-execution' join points, so your advice might get invoked twice. I think it's always good to restrict these broad pointcut expressions like '@within` or `@anotation()`. – Nándor Előd Fekete Mar 24 '16 at 12:21
  • And you're absolutely right, I forgot to include that part for the case when there's both a class-level and a method level annotation. I guess it's best to give the method-level annotation precedence in that case. – Nándor Előd Fekete Mar 24 '16 at 12:22
  • I couldn't find any concrete information on the difference between "method invocation" and "method execution". Do you have any more information on that "method lifecycle" and it's relevance to AOP? – ASA Mar 24 '16 at 12:35
  • Guess I got it: http://stackoverflow.com/questions/27376570/difference-between-call-and-execution-in-aop – ASA Mar 24 '16 at 12:38
  • Method execution is when a method gets executed. It doesn't care who called that method and it's only changing the class of the method in question. On the other hand, method call will focus on not the method itself, but on code invoking that method. So if you call a method from 100 places, it will weave your code in 100 places. But method call type join points are very useful, when you need to create logic depending on both the source and the target of the method call. – Nándor Előd Fekete Mar 24 '16 at 12:39
  • @Nándor Előd Fekete - I have followed your suggestion and it works fine, but I have this one problem: https://stackoverflow.com/questions/48243579/spring-aop-point-cut-not-getting-called Can you please help? – Partha Jan 13 '18 at 19:36
  • An excellent an elegant solution - **observation**: the current `adviseMethodsOfAnnotatedClass` advice method only works if `@X` is declared either on class or method scope and in different classes. It does not work if `@X` is declared in the class and method scopes within the same class. For that scenario `!annotatedMethod()` must be removed, and the behaviour changes to two nested arounds - the _method_ wrapping the _class_ advices. – Manuel Jordan Sep 14 '20 at 22:04
  • @ManuelJordan - I think I would use 3 pointcuts for this. "method && !class", "!method && class" and "method && class". For the last one, you could get either one annotation object, or both. Am I right? – ASA Sep 15 '20 at 10:36
  • @Traubenfuchs I did not test your solution, but through the current valuable solution is fine or enough remove `!annotatedMethod()` only if `@X` is used within the same class for class and method scopes/level – Manuel Jordan Sep 16 '20 at 16:10