14

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).

kriegaex
  • 63,017
  • 15
  • 111
  • 202
Stormcloud
  • 2,065
  • 2
  • 21
  • 41
  • possible duplicate of [Pointcut matching methods with annotated parameters](http://stackoverflow.com/questions/2766844/pointcut-matching-methods-with-annotated-parameters) – sheltem Apr 17 '15 at 09:54
  • @sheltem, Thanks for pointing me that this. Unfortunately this was one of the things I tried, but failed. The logs contained the error: Pointcut is not well-formed: expecting 'name pattern' at character position 56 execution(public * *(.., @aspects.VerifyMe(*), ..)) – Stormcloud Apr 17 '15 at 10:41
  • BTW: there are two stars seperated by a space after the word 'public' - stackoverflow has interpreted them as italic! – Stormcloud Apr 17 '15 at 10:48
  • You may want to edit your question to add that information then. Telling us what you tried so far and what happened/did not work helps to avoid repitition. – sheltem Apr 17 '15 at 11:11
  • Good point. I'll update – Stormcloud Apr 17 '15 at 13:14
  • 1
    I have edited the question: There is no such thing as a "cut-point", but the technical term in AOP is "pointcut". I also added syntax highlighting. I hope you do not mind. BTW, in one of your comments you say "cut-point method". If you mean the method which is bound to a pointcut and executed when a pointcut is intercepted, that one is called an "advice". :-) – kriegaex Apr 18 '15 at 07:24

2 Answers2

19

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
kriegaex
  • 63,017
  • 15
  • 111
  • 202
  • Awesome answer. You could add curlies around the body of the if. Currently the code that replaces the `// Verify 'arg' here or do whatever` will get executed on each iteration, which is what happened to me of course on Friday afternoon. – Bart Swennenhuis Jan 11 '19 at 19:40
  • Thank you, I have just added the redundant curly braces, but actually - no offense meant - copying & pasting my answer does not mean developers should switch off their brain while modifying the code. ;-) – kriegaex Jan 12 '19 at 04:33
2

This is what I ended up at after fiddling about with it (imports omitted):

@Aspect
public class VerifyAspect {

    @Before("execution(* *(.., @annotations.MyAnnotation (*), ..)) && args(.., verifyMe)")
    public void verifyInvestigationId(final Object verifyMe) {
        System.out.println("Aspect verifying: " + verifyMe);
    }
}

No need for anything Spring-specific, as AspectJ already provides you with the parameters if so desired.

sheltem
  • 3,754
  • 1
  • 34
  • 39
  • 1
    Nice (and certainly closer then anything I've managed to get working!), but this it does have a limitation that the argument passed as "verifyMe" is always the actual argument passed to the cut-point method. Is there a way to say something along the lines of "send me the argument(s) with the specified annotation"? – Stormcloud Apr 17 '15 at 15:14
  • You're right, this probably does not give you the annotated parameter in every case. Hmm... back to the drawing board! – sheltem Apr 17 '15 at 15:18
  • Though as an ad hoc solution, you could let AspectJ give you the JoinPoint and go through its parameters via reflection to find those annotated with your annotation. Not very elegant, but should work for a start. – sheltem Apr 17 '15 at 15:20
  • Come to think of it... there probably is no way to get specific annotated parameters via AspectJ, because you would run into ambiguity problems. Have a look at @kriegaex's answer here, he does it via reflection the way I suggested in my previous comment: http://stackoverflow.com/a/15605403/2191746 – sheltem Apr 17 '15 at 15:27
  • 1
    Yes, the way with reflection is the correct way to do this - not because it is my own answer you are pointing to in the previous comment but because of the ambiguity I explain in the answer with regard to the pointcut possibly matching multiple parameters. This is the only reason why you need to iterate over the joinpoint's arguments array and cannot bind it to a pointcut agrument directly. – kriegaex Apr 18 '15 at 07:21
  • Ah! :-( That's a shame because it sounds like something that would be generically a good thing to support. In my cause it's data validation (so I want to annotate the data to be validated) which is a great example of a cross cutting concern. @sheltem, thanks for taking the time to help – Stormcloud Apr 20 '15 at 07:22