Monday, January 22, 2007

Using AspectJ with Annotations

Recently I've been exploring annotations and how to use them with AspectJ. I've ran into a few things that I found to be odd, and may be due to the fact that annotations are still new to Java. Consider this example:

/* Required.java @Required annotation */
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.METHOD)
public @interface Required {
String[] names();
}


/*MyClass.java*/
import java.util.Map;
public class MyClass {

@Required(names = {"id","label"})
public static void myMethod(Map<String,String> args,Object obj)
{
//id and label should be guaranteed not null
}
}


The following aspect with the required pointcut will match against the method above like one would expect.

/*CheckRequired.aj*/
import java.util.Map;

public aspect CheckRequired {

protected pointcut required(Required required) :
execution(@Required * *(Map<String,String>,..))
&& @annotation(required);

before(Required required) : required(required)
{
Map<String,String> args = (Map)thisJoinPoint.getArguments()[0];
for (String name:required.names())
{
if (!args.containsKey(name))
throw new IllegalArgumentException(name + " is a required parameter.");
}
}
}

Instead of getting the arguments from thisJoinPoint we would like the arguments to be part of the pointcut like the following:

/*CheckRequired.aj*/
import java.util.Map;
public aspect CheckRequired {
protected pointcut required(Map<String,String> args, Required required) :
execution(@Required * *(Map<String,String>,*))
&& @annotation(required)
&& args(args);
before(Map<String,String> args, Required required) : required(args,required)
{
for (String name:required.names())
{
if (!args.containsKey(name))
throw new IllegalArgumentException(name + " is a required parameter.");
}
}
}

But when this is compiled we get the warning "advice defined in CheckRequired has not been applied". Why is this? Changing the method from static to non-static doesn't make a difference. You'll notice that MyClass.myMethod takes a second argument that is never used in this example. Removing the argument will cause the second example of CheckRequired.aj to advise that pointcut. I came across this problem with a method that actually does need a second argument so that is why I included it in this example.

No comments: