Annotation-driven aspects and Spring MVC

The built-in AspectJ support is a very strong selling-point for Spring and I’m a huge fan of aspect-oriented elegance. Generally though, I tend to approach it with what I consider to be a healthy dose of respect. It’s an immensly powerful tool, to be sure, but it also comes with the risk of making your system very hard to predict and to ultimately understand. Many IDE:s (like Eclipse) have good tooling available for resolving aspect applications at design-time, but it still makes it a lot more difficult for other programmers (and, ultimately, yourself) to reconstruct code-paths by just following method-calls.

One of my favourite patterns has become the annotation-driven aspect. Springs AspectJ supports pointcut declarations based on custom method or class annotations and to me, it’s the best of both worlds. Using annotations makes it clear which aspects are applied to which methods or classes, while at the same time giving you the non-intrusiveness if aspect oriented programming so you don’t have to rely on the tight coupling of inheritance or boiler-plate template code inside your methods.

When this pattern really comes to shine for me is in combination with Spring MVC. Even though request handlers in Spring MVC 2.5 are not forced to comply to a given interface, they nevertheless have a strong convention supported by the framework, ie a method returning a String, indicating a “redirect:” or a view mapping. Coupled together with the DispatcherServlets default behaviour of maintaining request attribute collections (and, by extension, the request itself) in a thread-local context, all relevant pieces of information are in place. So the basic pattern becomes:

  • A custom annotation and an aspect with a pointcut applied to all request handler methods with that annotation
  • An advise at that pointcut with the ability to access the current request parameters and modify it’s attributes, through the thread-local context
  • The ability to override which view or redirect to continue to before or after the controller
  • Any other processing you require

The same can be accomplished with interceptors, but due to the configuration limitation in Spring MVC, you can’t explicitly specify on a controller level which interceptors should be applied, so I find that the annotation-driven aspects becomes both clearer and much more flexible.

Other frameworks have different ways of enabling similar patterns. Struts 2 lets you specify the interceptor-stack by annotating your actions (in fact, it’s the core pattern of the entire framework). Many component-oriented frameworks (like Tapestry, Wicket, JSF etc) will give you secondary “hooks” into different steps of the component and request lifecycle where you can inject common functionality in a similar manner. With this pattern, you get the same power in Spring MVC.

A very simple example, a Trackable controller:

In this example, a controller wishes to register a request with some internal tracking service based on the existence of a cookie, whith the option of including a username, if one exists in the session.

First, the annotation:


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Trackable {
   String trackAction();
}

Second, annotate your controller


@Controller
public class SomeController {

   @RequestMapping("/some/url.do")
   @Trackable(trackAction="someUrlTrackAction")
   public String someUrlHandler() {
      return url;
   }
}

Lastly, the aspect that binds it all together

@Service
@Aspect
public class TrackableAspect {
   @Autowired
   private TrackingService trackingService;

   @Pointcut("@annotation(Trackable) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
   public void trackablePointcut() {
   }

   @Around("trackablePointcut()")
   public Object trackableAdvise(ProceedingJoinPoint pjp) {
      // RequestContextHolder managed by Spring MVC DispatcherServlet
      HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
      // Spring WebUtils
      Cookie cookie = WebUtils.getCookie(request, "trackingCookie");
      if (cookie != null) {
         // Get the trackAction specified on the annotation
         String trackAction = ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(Trackable.class).trackAction();
         String trackingCode = cookieookie.getValue();
         String userName = request.getSession().getAttribute("userName");
         if (userName != null) {
            trackingService.trackLoggedInAction(trackAction, trackingCode, userName);
         } else {
            trackingService.trackAction(trackAction, trackingCode);
         }
      }
      // continue with normal execution
      return pjp.proceed();
   }
}

As you can see, the around-advice will in this example always proceed with normal execution (ie, the @RequestMapping handler method) but there is nothing preventing you from ignoring normal execution in your advise and instead return some other value (which will then appear to the caller as the value returned from the advised method). As mentioned already, Spring MVC works on a convention-driven pattern so a handler method returning a String will forward execution to the view matching that string, or a String starting with “redirect:” will cause Spring MVC to send a client-side redirect response back to the browser.

Advertisements

One Response to Annotation-driven aspects and Spring MVC

  1. Kermit says:

    “Annotation-driven aspects and Spring MVC PAP – Java and Web development” was in fact a fantastic blog post and therefore I was in fact very content to read
    the blog. Thanks for your time-Terri

Leave a Reply

Fill in your details below or click an icon to log in:

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: