Search code examples
javaspringannotationsaspectjspring-aop

How to write an Aspect pointcut based on an annotated parameter


I'm having a bit of trouble working out how to create a pointcut that will operate on beans that have a specific annotated parameter. My eventual aim is to validate the value of the parameter before it's processed, but for the moment I just need to create the pointcut.

Consider the following annotation

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.PARAMETER })
public @interface MyAnnotation {}

I'd then like to apply this to a number of methods like:

public void method1(@MyAnnotation long i) {}
public void method2(String someThing, @MyAnnotation long i) {}
public void method3(String someThing, @MyAnnotation long i, byte value) {}

So

  • I don't care which class (or package) the methods are in
  • The position of the annotated argument will vary.
  • I do know that annotated value will only apply to a specific type

My pointcut implementation needs to be something along the lines of:

@Before(value = "* *(..) && args(verifyMe)")
public void verifyInvestigationId(long verifyMe) {}

I'm getting a bit confused about exactly what that @Before value needs to be and how to tie in the annotation and its type. At this point it's probably not worth listing the things I've tried!

Update: Based on the advice I've seen in http://stackoverflow.com/questions/3565718/pointcut-matching-methods-with-annotated-parameters/3567170#3567170 (and correcting a couple of misunderstandings and adding space I overlooked) I've got to the point where the following works:

@Before("execution(public * *(.., @full.path.to.MyAnnotation (*), ..))")
public void beforeMethod(JoinPoint joinPoint) {
    System.out.println("At least one of the parameters are annotated with @MyAnnotation");
}

This is almost what I need - all I need to do is pass the value of the annotated argument as an parameter to the method. I can't quite work out the syntax to get Spring to do this (the linked answer does not show this).


Solution

  • Very similar to my answer here which sheltem already pointed to, the solution looks like this (in annotation-style syntax this time because in Spring AOP you cannot use native AspectJ syntax):

    Original poster's annotation:

    package annotations;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.PARAMETER })
    public @interface MyAnnotation {}
    

    Driver Application:

    I use the driver application in order to test my AspectJ solution. In Spring the class as well as the aspect need to be Spring beans/components in order for this to work.

    package de.scrum_master.app;
    
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    
    import annotations.MyAnnotation;
    
    public class Application {
        public void method1(@MyAnnotation int i) {}
        public void method2(String id, @MyAnnotation float f) {}
        public void method3(int i, @MyAnnotation List<String> strings, @MyAnnotation String s) {}
        public void method4(int i, @MyAnnotation Set<Integer> numbers, float f, boolean b) {}
        public void method5(boolean b, String s, @MyAnnotation String s2, float f, int i) {}
        public void notIntercepted(boolean b, String s, String s2, float f, int i) {}
    
        public static void main(String[] args) {
            List<String> strings = new ArrayList<String>();
            strings.add("foo");
            strings.add("bar");
            Set<Integer> numbers = new HashSet<Integer>();
            numbers.add(11);
            numbers.add(22);
            numbers.add(33);
    
            Application app = new Application();
            app.method1(1);
            app.method2("foo", 1f);
            app.method3(1, strings, "foo");
            app.method4(1, numbers, 1f, true);
            app.method5(false, "foo", "bar", 1f, 1);
            app.notIntercepted(false, "foo", "bar", 1f, 1);
        }
    }
    

    Aspect:

    package de.scrum_master.aspect;
    
    import java.lang.annotation.Annotation;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.SoftException;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.reflect.MethodSignature;
    
    import annotations.MyAnnotation;
    
    @Aspect
    public class ArgCatcherAspect {
        @Before("execution(public * *(.., @MyAnnotation (*), ..))")
        public void interceptMethodsWithAnnotatedParameters(JoinPoint thisJoinPoint) {
            System.out.println(thisJoinPoint);
            MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
            String methodName = signature.getMethod().getName();
            Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
            Annotation[][] annotations;
            try {
                annotations = thisJoinPoint.getTarget().getClass().
                    getMethod(methodName, parameterTypes).getParameterAnnotations();
            } catch (Exception e) {
                throw new SoftException(e);
            }
            int i = 0;
            for (Object arg : thisJoinPoint.getArgs()) {
                for (Annotation annotation : annotations[i]) {
                    if (annotation.annotationType() == MyAnnotation.class) {
                        System.out.println("  " + annotation + " -> " + arg);
                        // Verify 'arg' here or do whatever
                    }
                }
                i++;
            }
        }
    }
    

    Console log:

    execution(void de.scrum_master.app.Application.method1(int))
      @annotations.MyAnnotation() -> 1
    execution(void de.scrum_master.app.Application.method2(String, float))
      @annotations.MyAnnotation() -> 1.0
    execution(void de.scrum_master.app.Application.method3(int, List, String))
      @annotations.MyAnnotation() -> [foo, bar]
      @annotations.MyAnnotation() -> foo
    execution(void de.scrum_master.app.Application.method4(int, Set, float, boolean))
      @annotations.MyAnnotation() -> [33, 22, 11]
    execution(void de.scrum_master.app.Application.method5(boolean, String, String, float, int))
      @annotations.MyAnnotation() -> bar