Friday, 9 September 2011

Using Spring's AspectJ Support and the @Before Annotation

You may have wondered how the Guys at Spring do all that jiggery-pokery with the annotations that you add to your Spring beans. I’m not an expert here, but I suspect that somewhere along the way, they do a little Aspect Oriented Programming using AspectJ - after all, they do have comprehensive support for it. This of course is total supposition - I’ve not been trawling through their source code. This blog demonstrates how you can use AspectJ to intercept your class's annotated methods and to do that,I’m going to demonstrate the @Before annotation by writing a Before Advice class that takes a peak at the attributes of a method’s annotations. This blogs builds on my previous AspectJ blog that demonstrates how to write an After Throwing advice.

The first thing we really need is an annotation. Any thing will do, and I’ve written a simple @TestAnnotation as demonstrated below. This has one attribute: value:

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 TestAnnotation {
 
String value();
}

This annotation can then be applied to any method in your application - choose one at random here as the annotation doesn’t really do anything:

public class MySimplePrintln {

 
/**
   * Print a message
   */
 
@TestAnnotation(value = "Roger")
 
public void println(String arg0) {
   
System.out.println(arg0);
 
}
}

The next class is the heart of the matter. It’s a Before Advice that checks the execution of any method which is annotated with @TestAnnotation as demonstrated in the snippet below:

  @Before("execution(* *.*(..)) && @annotation(testAnnotation) ")
 
public void myBeforeLogger(JoinPoint joinPoint, TestAnnotation testAnnotation) {

My previous blog explored the execution() expression in detail; however, the thing to note here is that it’s combined with the annotation expression: @annotation(testAnnotation) so as create the correct method filtering. The next thing to note is that the annotation is passed to the myBeforeLogger(..) as an argument. This allows us to get hold of the annotation’s attributes as demonstrated in the complete method source code below:

@Aspect
public class BeforeAdvice {

 
// Obtain a suitable logger.
 
private static Log logger = LogFactory.getLog(BeforeAdvice.class);

 
@Before("execution(* *.*(..)) && @annotation(testAnnotation) ")
 
public void myBeforeLogger(JoinPoint joinPoint, TestAnnotation testAnnotation) {

   
System.out.println("Okay - we're in the before handler...");
    System.out.println
("The test annotation value is: " + testAnnotation.value());

    Signature signature = joinPoint.getSignature
();
    String methodName = signature.getName
();
    String stuff = signature.toString
();
    String arguments = Arrays.toString
(joinPoint.getArgs());
    logger.info
("Write something in the log... We are just about to call method: "
       
+ methodName + " with arguments " + arguments + "\nand the full toString: "
       
+ stuff);

 
}

}

So, not only can you access a method’s arguments before it’s called, you can also access its annotation’s attributes, which to me seems a pretty powerful tool.

You’ll also need a Spring config file and some code to load it, but I’ve covered that in my previous blog on Spring and AspectJ, so take a look here for more information. The only key point here is that you need to include the line:

<aop:aspectj-autoproxy/>

...somewhere in your config file.

Running this code above will produce the following output:

This is the before handler
13:54:10,110  INFO FileSystemXmlApplicationContext:456 - Refreshing org.springframework.context.support.FileSystemXmlApplicationContext@7a3570b0: startup date [Wed Aug 17 13:54:10 BST 2011]; root of context hierarchy
13:54:10,262  INFO XmlBeanDefinitionReader:315 - Loading XML bean definitions from file [/Java_Projects2/Tips/marin-tips-spring-3/src/main/resources/example10_beforeadvice.xml]
13:54:10,939  INFO DefaultListableBeanFactory:555 - Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@30296f76: defining beans [org.springframework.aop.config.internalAutoProxyCreator,beforeHandler,mainClass,println]; root of factory hierarchy
Okay - we're in the before handler...
The test annotation value is: Roger
13:54:11,790  INFO BeforeAdvice:43 - Write something in the log... We are just about to call method: println with arguments [Something to print...]
and the full toString: void example_10_annotations.before_annotation_annotation.MySimplePrintln.println(String)
Something to print...
the end...

Finally, there’s more. Given that the @Before’s default attribute is an expression, you can apply more than one annotation to your advice method:

  @Before("execution(* *.*(..)) && @annotation(testAnnotation) @annotation(requestMethod)")
 
public void myBeforeLogger(JoinPoint joinPoint, TestAnnotation testAnnotation,
      RequestMethod requestMethod
) {

No comments: