10

I need to create an aspect with a pointcut matching a method if:

  1. it is annoted with MyAnnotationForMethod
  2. One of its parameters (can have many) is annotated with @MyAnnotationForParam (but can have other annotations as well).

The aspect class look like this

@Pointcut("execution(@MyAnnotationForMethod * *(..,@aspects.MyAnnotationForParam Object, ..)) && args(obj)")
void myPointcut(JoinPoint thisJoinPoint, Object obj) {
}

@Before("myPointcut(thisJoinPoint ,  obj)")
public void doStuffOnParam(JoinPoint thisJoinPoint, Object obj) {
    LOGGER.info("doStuffOnParam :"+obj);
}

The annoted method

@MyAnnotationForMethod
public string theMethod(String a, @MyAnnotationForParam @OtherAnnotation Object obj, Object b){ 
    LOGGER.info(a+obj+b);
}

With eclipse -> warnings : On the poincut :

Multiple markers at this line 
    - no match for this type name: MyAnnotationForMethod [Xlint:invalidAbsoluteTypeName] 
    - no match for this type name: aspects.MyAnnotationForParam On the before : advice defined in xxx.xxx.xxx.xxx.MyAspect has not been applied [Xlint:adviceDidNotMatch]

Using last aspectJ plugin from http://download.eclipse.org/tools/ajdt/35/update

With maven command line using aspectj 1.6.9

[WARNING] no match for this type name: MyAnnotationForMethod [Xlint:invalidAbsoluteTypeName]
[WARNING] no match for this type name: aspects.MyAnnotationForParam [Xlint:invalidAbsoluteTypeName]
[WARNING] advice defined in xxx.xxx.xxx.xxx.MyAspect has not been applied [Xlint:adviceDidNotMatch]

The annotations :

package com.xxx.xxx.annotation;
// standard imports stripped
@Documented
@Target( { FIELD, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface @MyAnnotationForParam {}

and

package com.xxx.xxx.annotation;
// standard imports stripped
@Target(METHOD)
@Retention(RUNTIME)
@Documented
public @interface MyAnnotationForMethod {}

And of course it doesn' work properly.

Can you tell me what is wrong ?

thx.

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
Patrick Sauts
  • 103
  • 1
  • 1
  • 6

2 Answers2

15

Updated:

OK, the best reference I could find is on this page: Annotations, Pointcuts and Advice.

You can match the method, however you won't be able to catch the parameter (just the method and the annotation). So what you will have to do is a combination of pointcut matching and reflection. Something like this:

@Pointcut(
    "execution(@com.xxx.xxx.annotation.MyAnnotationForMethod * *(.., @com.xxx.xxx.annotation.MyAnnotationForParam (*), ..))")
public void annotatedMethod(){}

@Before("annotatedMethod()")
public void doStuffOnParam(final JoinPoint jp){
    final Signature signature = jp.getSignature();
    if(signature instanceof MethodSignature){
        final MethodSignature ms = (MethodSignature) signature;

        final Method method = ms.getMethod();
        final String[] parameterNames = ms.getParameterNames();
        final Class<?>[] parameterTypes = ms.getParameterTypes();
        final Annotation[][] parameterAnnotations =
            method.getParameterAnnotations();
        for(int i = 0; i < parameterAnnotations.length; i++){
            final Annotation[] annotations = parameterAnnotations[i];
            final MyAnnotationForParam paramAnnotation =
                getAnnotationByType(annotations, MyAnnotationForParam.class);
            if(paramAnnotation != null){
                this.processParameter(ms.toShortString(),
                    parameterNames[i],
                    parameterTypes[i],
                    paramAnnotation);
            }

        }
    }
}

/**
 * In an array of annotations, find the annotation of the specified type, if any.
 * @return the annotation if available, or null
 */
@SuppressWarnings("unchecked")
private static <T extends Annotation> T getAnnotationByType(final Annotation[] annotations,
    final Class<T> clazz){

    T result = null;
    for(final Annotation annotation : annotations){
        if(clazz.isAssignableFrom(annotation.getClass())){
            result = (T) annotation;
            break;
        }
    }
    return result;
}

/**
 * Do some processing based on what we found.
 * @param signature method signature
 * @param paramName parameter name
 * @param paramType parameter type
 * @param paramAnnotation annotation we found
 */
private void processParameter(final String signature,
    final String paramName,
    final Class<?> paramType,
    final MyAnnotationForParam paramAnnotation){

    System.out.println(MessageFormat.format(
        "Found parameter ''{0}'' \n  of type ''{1}'' \n  with annotation ''{2}'' \n  in method ''{3}''",
        paramName,
        paramType,
        paramAnnotation,
        signature));
}

Here is my test class for the above aspect:

public class TestClass{

    @MyAnnotationForMethod
    public void simpleTestMethod(@MyAnnotationForParam final String param1){
        System.out.println("Method body (simple)");
    };

    @MyAnnotationForMethod
    public void complexTestMethod(final String param1,
        @MyAnnotationForParam final Float param2,
        @MyAnnotationForParam final Boolean param3){
        System.out.println("Method body (complex)");
    };

    public static void main(final String[] args){
        System.out.println("Starting up");
        final TestClass testObject = new TestClass();
        testObject.simpleTestMethod("Hey");
        testObject.complexTestMethod("Hey", 123.4f, false);
        System.out.println("Finished");
    }

}

and here is the output:

Starting up
Found parameter 'param1' 
  of type 'class java.lang.String' 
  with annotation '@com.xxx.xxx.annotation.MyAnnotationForParam()' 
  in method 'TestClass.simpleTestMethod(..)'
Method body (simple)
Found parameter 'param2' 
  of type 'class java.lang.Float' 
  with annotation '@com.xxx.xxx.annotation.MyAnnotationForParam()' 
  in method 'TestClass.complexTestMethod(..)'
Found parameter 'param3' 
  of type 'class java.lang.Boolean' 
  with annotation '@com.xxx.xxx.annotation.MyAnnotationForParam()' 
  in method 'TestClass.complexTestMethod(..)'
Method body (complex)
Finished

Hint

You will probably want to cache a lot of this, there is no need to parse every parameter of every annotation in every execution. Keep a map of which parameter of which method carries the annotation and process only those parameters.

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
  • Sean, Any idea why you need the full classpath to your own defined annotation. This is what has been doing me in for the last 2 hours. Thanks – Bill Comer Jun 29 '11 at 14:25
  • @Bill the problem is that aspectj usually works on compiled classes, not sources. And imports are not stored in classes. So there is not really a way to guess the qualified name from a class name found in a pointcut annotation. – Sean Patrick Floyd Jul 07 '11 at 21:12
1

The ms.getParameterNames() call in the above solution doesnt seem to work when the method is implemented from an interface. I get back nulls.

However, if I enable CGLIB, then it works.

<aop:aspectj-autoproxy proxy-target-class="true"/>
Darth Ninja
  • 1,049
  • 2
  • 11
  • 34
  • 1
    @Nittin parameter names are only available if you [compile your classes with the -g flag](http://download.oracle.com/javase/6/docs/technotes/tools/windows/javac.html) – Sean Patrick Floyd Jul 07 '11 at 21:17
  • @SeanPatrickFloyd actually, using proxying target classes also allows to get parameter annotations. Until I've added this setting, I always got empty array. – uthark Feb 14 '12 at 06:59