Search code examples
javaspring-bootannotationsaspectjspring-aop

Intercept annotated classes and methods in Spring AOP or AspectJ


So I have a custom annotation

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Intercepted {}

that I want to use to weave aspects into methods (AspectJ, @annotation(Intercepted)).

The idea is that I weave the aspect in when I annotate the method @Intercepted directly -- that part works -- or if I annotate the class, the aspect should be weaved into all its (public) methods -- that part doesn't.

Furthermore, if I annotate a class and one of its methods, the aspect should only get weaved in once, the method-level annotation overriding the class-level one.

Essentially, I want an "add the class-level annotation if there's a class-level annotation, but only if there isn't already a method-level annotation."

How do I do that?


Solution

  • Here is an AspectJ example. The pointcut syntax is the same in Spring AOP.

    Helper classes:

    package de.scrum_master.app;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Intercepted {}
    
    package de.scrum_master.app;
    
    @Intercepted
    public class AnnotatedClass {
      public void doSomething() {}
      public void doSomethingElse() {}
    }
    
    package de.scrum_master.app;
    
    public class AnnotatedMethod {
      @Intercepted
      public void doSomething() {}
      public void doSomethingElse() {}
    }
    
    package de.scrum_master.app;
    
    @Intercepted
    public class AnnotatedMixed {
      @Intercepted
      public void doSomething() {}
      public void doSomethingElse() {}
    }
    

    Driver application (Java SE, no Spring):

    package de.scrum_master.app;
    
    public class Application {
      public static void main(String[] args) {
        // Should be logged
        new AnnotatedClass().doSomething();
        // Should be logged
        new AnnotatedClass().doSomethingElse();
    
        // Should be logged
        new AnnotatedMethod().doSomething();
        // Should NOT be logged
        new AnnotatedMethod().doSomethingElse();
    
        // Should be logged, but only once
        new AnnotatedMixed().doSomething();
        // Should be logged
        new AnnotatedMixed().doSomethingElse();
      }
    }
    

    Aspect:

    Please note that the execution(* *(..)) && part is not necessary in Spring AOP because only method execution joinpoints are supported there. The pointcut could just be annotatedMethod() || annotatedClass() there. In AspectJ I have to be more precise because otherwise other joinpoint types would be logged.

    package de.scrum_master.aspect;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    
    @Aspect
    public class AnnotationInterceptor {
      @Pointcut("@annotation(de.scrum_master.app.Intercepted)")
      public void annotatedMethod() {}
    
      @Pointcut("@within(de.scrum_master.app.Intercepted)")
      public void annotatedClass() {}
    
      @Before("execution(* *(..)) && (annotatedMethod() || annotatedClass())")
      public void log(JoinPoint thisJoinPoint) {
        System.out.println(thisJoinPoint);
      }
    }
    

    Console log:

    execution(void de.scrum_master.app.AnnotatedClass.doSomething())
    execution(void de.scrum_master.app.AnnotatedClass.doSomethingElse())
    execution(void de.scrum_master.app.AnnotatedMethod.doSomething())
    execution(void de.scrum_master.app.AnnotatedMixed.doSomething())
    execution(void de.scrum_master.app.AnnotatedMixed.doSomethingElse())