Esquire Theme by Matthew Buchanan
Social icons by Tim van Damme

14

May

The Python @ decorator pattern for Java using AspectJ

How to make AspectJ + Java @ Annotations like Python Decorators

There are many times where you want to write a block of code and have some boilerplate code happen before and after. Or you may want to register that block of code as some sort of listener/handler. I found this to be perhaps one of the most common patterns in web application development other than maybe the iterator pattern.

An example might be to log the before an after of a method or maybe make the method retry (recall) itself on failure a certain number of times (reconnecting to a resource on a network).

Now you could just handle this problem by calling the boilerplate code or you could use decorators in Python or AOP in Java. Most pythonist know about decorators and they are easy to understand so I want go over them here. Actually that is one of the huge advantages of python’s decorator’s they are much easier to understand than AspectJ. Fear not I will show you how to do Python’s decorator pattern in Java and show why its awesome declarative programming.

Java’s ‘@’ annotations are technically very different than Python’s ‘@’ decorators even though they do share semantical and syntactical similarities. Java’s @ is declarative because you are just adding meta-data and unlike Python you don’t get any runtime behavior change. WTF meta data is boring I want behavior change! Have no fear I will show you how to add behavior to those annotations using AspectJ.

One of the most useful Python like decorator’s that we use on evocatus.com is our @AlertTimer annotation which will log an error message any time a method runs too long (error messages are emailed to use). Whats awesome about annotations is they have optional keyword like arguments. I know Pythonist are laughing but in the Java world we have never had such luxury.

Below is an example of a Java annotation + AspectJ that will log an error if the method takes too long to execute:

@Retention(RetentionPolicy.RUNTIME)
public @interface AlertTimer {
public static long DEFAULT_TIME = 3000L;
public long value() default DEFAULT_TIME;
public boolean ignore() default false;
}

public aspect AlertTimerAdvice {

public static long MAX_TIME = AlertTimer.DEFAULT_TIME;

private Log log = LogFactory.getLog("AlertTimer");
pointcut requestMapping() : execution(@RequestMapping * * (..));
pointcut alertTimer() : execution(@AlertTimer * * (..));

Object around() : requestMapping() || alertTimer() {
long start = System.currentTimeMillis();
try {
return proceed();
} finally {
long end = System.currentTimeMillis();
MethodSignature ms = (MethodSignature) thisJoinPoint.getSignature();
AlertTimer at = ms.getMethod().getAnnotation(AlertTimer.class);
long max = MAX_TIME;
if (at != null && ! at.ignore() )
max = at.value();
recordTime(start, end, max,
thisJoinPointStaticPart.getSignature());
}
}

@SuppressWarnings("unused")
private void recordTime(long start, long end, long max, Signature sig) {
long time = end - start;
if (end - start > max) {
log.error(sig.toLongString() + " took to long: " + time + "ms");
}
}

}