Search code examples
javaspringaspectjspring-aoppointcut

Spring AOP Pointcut expression for custom annotation in subclass


I am working on a logging aspect which need to intercept all the classes and methods annotated with a custom annotation.

Below is custom annotation class which can be annotated on class and methods:

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Loggable {
    LogLevel value();
}

I am using these pointcut expressions to intercept methods and classes with annotation @Loggable, which is working for all the simple classes but does not work for classses which extend or implement.

//Works for annotation @Loggable on class level
@Pointcut("execution(* *(..)) &&  within(@com.logger.Loggable *)")
public void classAnnotationLogger() {
}

//Working for methods annotated with @Loggable
@Before(value = "@annotation(loggable)", argNames = "jp, loggable")
public void logBeforeAdvice(JoinPoint jp, Loggable loggable) {
  ..
  ..
}

Below is code for super class

@Component
@Loggable(LogLevel.INFO)
public abstract class Processor{
  public void process(){
      readProcess();
  }

  public abstract void readProcess();
}

Below is code for subclass

@Service
@Loggable(LogLevel.INFO)
public class MyServiceProcessor extends Processor {

  @Override
  public void readProcess(){
    ...
    ...
  }
}

In the application readProcess() is called by doing

Processor processor = applicationContext.getBean(MyServiceProcessor.class);
processor.readProcess();

Even though I have @Loggable on Processor and MyServiceProcessor, when readProcess is called the advice is not being invoked.

But advice is invoked for process() and not readProcess.

How do I write the pointcut expression which also intercepts the call to any subclass methods, when annotation @Logabble is applied on any class or method?


Solution

  • Well, first of all this

    @Pointcut("execution(* *(..)) &&  within(@com.logger.Loggable *)")
    public void classAnnotationLogger() {}
    

    is just a pointcut and not an advice, so it does not do anything unless you also have an advice actually using this pointcut. You have not posted such an advice, so I can only speculate.

    Secondly, you have not provided any sample code which would be triggered by

    @Before(value = "@annotation(loggable)", argNames = "jp, loggable")
    public void logBeforeAdvice(JoinPoint jp, Loggable loggable) {}
    

    at all, i.e. no annotated method. Your sample code only shows annotated classes.

    As for the @Loggable annotation on the subclass, it should not be necessary because its base class already carries the same annotation and the annotation is @Inherited. This works for annotations on classes, but not for annotations on methods or interfaces, see my other answer for an explanation and a possible workaround.

    This example of yours should actually work, I cannot see a reason why it would not:

    Processor processor = applicationContext.getBean(MyServiceProcessor.class);
    processor.readProcess();
    

    But this internal call to readProcess() (equivalent to this.readProcess()) will not work:

    public void process() {
        readProcess();
    }
    

    This is because Spring AOP is a proxy-based "AOP lite" framework relying on JDK dynamic proxies (for interfaces) or CGLIB proxies (for classes). But calls to this.someMethod() are not routed through proxies of any type so they cannot be intercepted by Spring aspects. This is documented behaviour. If you want to overcome this limitation and apply aspects to internal method calls as well, please use full-blown AspectJ as documented here.