Saturday, May 16, 2009

Extending Spring MVC's annotation controller

In my latest project I am using Spring MVC's annotation based controller. I definitely am a fan of annotations for wiring up web applications, and suppose, relatively speaking, I can claim to be an early in this area having created Strecks, an annotation based framework for Struts.

I must say I am enjoying using the new Spring MVC - it's a massive improvement over the original framework which I found pretty clunky, especially with regard to form handling.

Setup

Configuring the application is a doddle. All you need to do is register the annotation HandlerAdapter (which does the main request processing work) and HandlerMapping (which maps URLs to your controllers). You can do this using Spring config like:

<bean class="org.springframework.web.servlet.mvc.
annotation.DefaultAnnotationHandlerMapping">

<bean class="org.springframework.web.servlet.mvc.
annotation.AnnotationMethodHandlerAdapter">

and then you're ready to go. Controllers definitions can found automatically via class path scanning, or  added explicitly into the Spring config files, which I prefer to do.

One really nice thing about the controllers is the simple way to map URLs to methods as well as to provide arguments to the methods, both using annotations. An example is show below:
@RequestMapping("/warehouse/postProductsSubmit.htm")
public String postProductsSubmit(
      Map model,
      @ModelAttribute("command") PostProductsForm command,
      BindingResult result) {
//do stuff

//redirect when finished
return "redirect:postProductsForm.htm";
}

So what's missing?

There were still a few bits I felt needed to be added to make the Spring MVC annotation truly usable for my application. Here's what they are.

Missing annotations for obvious argument types

The Spring MVC annotations recognise a whole bunch of argument types. Many of these will be automatically recognised from the Servlet API including HttpServletRequest, HttpServletResponse, ServletRequest, ServletResponse, HttpSession, Principal, Locale, InputStream, Reader, OutputStream and Writer. Others will be recognised from Spring MVC annotations, such as @ModelAttribute and @RequestParam (which binds a request parameter).

What would be nice would be some built in annotation types which you could extract other types of information from the Servlet API environment in an non-intrusive way. Here I am thinking of the following:
  • @SessionAttribute: extract and bind a named session attribute.
  • @RequestAttribute: do the same for a named request attribute.
  • @RequestHeader: extract a request header.
  • Plus various others
Luckily, there is an easy way of creating your own annotations for extracting information from requests and binding this to method arguments, via the WebArgumentResolver implementation. I have created a number of these in the Impala MVC extensions project. See for example the SessionAttributeArgumentResolver and it's associated @SessionAttribute annotation.

Flash Scope

Flash scope, popularised initially by Rails, is a mechanism for transferring state from one request to the next without having to pass it via URLs. It is implemented through a session scoped attribute which is removed as soon as the value is consumed in the subsequent request. It works particuarly well with redirecting after a post.

Flash scope is especially convenient for certain use cases because it combines the convenience of session-based attributes without the long running overhead of having state hanging around in a session over a long period.

Spring MVC annotations currently don't support flash scope, so added an extension to AnnotationMethodHandlerAdapter which support flash scope. Basically you can use it as follows. In your controller method, simply set a model attribute with the prefix "flash:". The attribute will be available in the next request using the @RequestAttribute annotation. An example below demonstrates this.
@RequestMapping("/submit.htm")
public String submit(
      Map model) {
//do stuff

//redirect when finished
model.put("flash:mydata", mydataObject);
return "redirect:show.htm";
}

@RequestMapping("/show.htm")
public void(@RequestAttribute("mydata") MyDataClass mydata) {
//if you redirected using flash, mydata 
//will contain the mydataObject instance
//from the last call
}

No subclass hooks for manipulating model

When I first started with the annotation-based controller I found it a little frustrating that there were no subclass hooks in the provided AnnotationMethodHandlerAdapter for manipulating the model. The only way you can do this is in the mapped request method as well as in special @ModelAttribute methods, which are also present in your controllers. An example is below:

@ModelAttribute("command")
public PostProductsForm getPostProductsForm() {
return new PostProductsForm();
}
 

I'm not sure if this is still a problem because I have found quite acceptable workarounds which solve the problems I was trying to solve, without having to resort to such a technique. Nevertheless, is does strike me as a sensible thing to be able to do, provided it is is done in a well-defined and controlled way.

Concluding Remarks

Spring MVC annotations have added a great deal of convenience to Spring MVC without sacrificing any of the flexibility which has always been its true strength. It's not perfection, but with a few extra fairly minor features it is easy to use and work very productively with. And of course - here comes the obligatory shameless plug! - it works even better when you use it with Impala.

5 comments:

Freekeswar said...
This comment has been removed by the author.
Freekeswar said...

I feel its easier and better to use annotations instead of extending the SimpleFormController class. Very nice tutorial indeed.

regards,
Eswar.
Vaannila

Anonymous said...

I've noticed the parameters I add using model.addAttribute("flash:var",var) , append 'var' to the querystring. Is there any way to avoid this, or does it happen before the Annotation handler gets hold of it.
Thanks for the example.

Anonymous said...

When we add using model.addAttribute("flash:successMessage","Information is successfully updated") , append "Information is successfully updated" to the query string.
Is there any way to avoid this?

Anonymous said...

awesome!
Thank you!