Liz Douglass

Adding a Spring aspect

leave a comment »

Yesterday Tom and I added a Spring aspect to our front end project. My only previous encounter with aspects was when I read a Spring book about a year ago. So, what is an aspect? According to the documentation “Aspects enable the modularization of concerns such as transaction management that cut across multiple types and objects”.

Tom and I needed to redirect the user to an ‘eek’ page if they tried to do some transactions when their account was in a bad way. An aspect seemed like a tool for the job.  But what sort of aspect? As Tom explained to me, the Around advice is the only type that can be used to change the flow of execution:

Around advice: Advice that surrounds a join point such as a method invocation…. Around advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.

We created the new aspect in these steps:

1. Create a new method annotation. This annotation was applied to all the controller method join points. According to the Spring documentation this is what is known as an “@annotation pointcut designator – the subject of the join point matches only methods that have the given annotation.”

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TransactionPermitted {
}

2. Create the aspect using TDD. Our aspect checks to see whether the the users account is in a state and redirects to the “transactions-not-permitted” view if this is the case. We used the example in the documentation to create our aspect. Tom pointed out a couple of thing while we were doing this that I think are important to remember:

  • Aspects need a no argument constructor.
  • From the docs: “The value returned by the around advice will be the return value seen by the caller of the method”. We had to make sure that all the handler methods advised by this aspect also return a string (and not a ModelAndView for example).
@Aspect
@Component
public class TransactionPermittedAspect {

private final Logger logger = Logger.getLogger(TransactionPermittedAspect.class);

private FooRepository fooRepository;

@Autowired
public void setFooRepository(FooRepository fooRepository) {
this.fooRepository = fooRepository;
}

@Around("@annotation(my.package.TransactionPermitted)")
public Object intercept(ProceedingJoinPoint pjp) throws Throwable {
logger.debug("Checking whether transactions are permitted");

Widget widget = AspectUtils.getArgumentOfType(pjp.getArgs(), Widget.class);
Bar bar = fooRepository.getBar(widget);

if (bar.hasASomeBadCondition()) {
return "transactions-not-permitted";
}
return pjp.proceed();
}

}

Our aspect (above) uses AspectUtils. This class, created by Tom, abstracts the argument searching and casting:

public class AspectUtils {

public static <T> T getArgumentOfType(Object[] args, Class<T> type) {
for (Object arg : args) {
if (type.isInstance(arg)) {
return type.cast(arg);
}
}
throw new IllegalArgumentException("Cannot find argument of " + type);
}
}

3. Apply the annotation pointcut to the target handler methods. This step was trivial, but adding them using TDD was interesting. Each class with a method annotated by our pointcut now has a test like this:

@Test
public void shouldCheckThatTransactionsArePermittedForFoo() {
assertNotNull("Transaction permission check not found", myMethod().getAnnotation(TransactionPermitted.class));
}

private Method myMethod() {
return MethodFinder.findRequestHandlerMethod(WibbleController.class, "myMethod");
}

Note that the MethodFinder looks for the target method with the a particular name and a RequestMapping annotation:

import static org.junit.Assert.fail;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.reflect.Method;

public class MethodFinder {

public static Method findRequestHandlerMethod(Class<?> type, String methodName) {
Method[] methods = type.getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName) && (method.getAnnotation(RequestMapping.class) != null)) {
return method;
}
}
fail("Cannot find method named [" + methodName + "] with RequestMapping annotation in " + type.getName());
return null;
}
}

}

Adding this around aspect took us a very short time and it perfectly suited our need.

Advertisements

Written by lizdouglass

December 16, 2009 at 10:19 am

Posted in Uncategorized

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: