Using a Hybrid Annotations & XML Approach for Request Mapping in Spring MVC

In Spring 2.5 it is possible to use annotations to configure all parts of a web application. Seeing annotations applied is particularly interesting in the Web layer where developers traditionally rely on the SimpleFormController and the MultiActionController for form page handling. The introduction of annotations has created a third option, one that does not require a base class while still offering the flexibility of previous approaches.
While it is easy to see the elegance in using annotated POJOs to implement Controllers, the benefit is not as clear in the area of URL-to-Controller mappings. What would it be like to define all your URL mapping rules using annotations? Indeed this is one area in which centralized configuration has worked well for developers of Spring MVC applications.
Lets review the Spring 2.0 options for URL-to-Controller mappings:
- The bean name approach (BeanNameUrlHandlerMapping). The name of each bean holds the path it serves. Despite its simplicity, this approach can scale if combined with coarse-grained servlet mappings (e.g. "/browse/*", "/order/*", "/reports/*", etc.).
- The centralized approach (SimpleUrlHandlerMapping). A central place to see URL patterns and controller mappings.
- The convention over configuration approach (ClassNameUrlHandlerMapping). Matches URL paths to class names. Hence "/accounts/*" is mapped to a MultiActionController of type AccountsController. No explicit mappings are required.
Spring 2.5 adds a fourth option in the form of the @RequestMapping annotation, which can be placed on a class or a method. When placed on both the method-level mapping narrows the class-level mapping.
Here is a SimpleFormController-style workflow in which the method-level mapping is narrowed down by request method:
@RequestMapping("/editAccount")
public class EditAccountController {
@RequestMapping(method=RequestMethod.GET)
public Account setupForm(@RequestParam("id") Long id) {
…
return account;
}
@RequestMapping(method=RequestMethod.POST)
public String processSubmit(Account account) {
…
return "redirect:/success.htm";
}
}
And here is a MultiActionController-style delegation in which the method-level mapping is narrowed down by both request method and relative path:
@RequestMapping("/accounts/*")
public class AccountsController {
@RequestMapping(method=RequestMethod.GET)
public List<Account> list() {…}
@RequestMapping(method=RequestMethod.GET)
public Account show(@RequestParam("id") Long id) {…}
@RequestMapping(method=RequestMethod.POST)
public String create(Account account) {…}
…
}
As you can see, with the "/accounts/*" mapping embedded in the code it can be difficult to enforce an application-wide convention for Controller mapping. At least not without some rigorous discipline. Fortunately there is a way to combine an external HandlerMapping with method-level @RequestMapping annotations. Below is an example illustrating how this approach works:
<property name="mappings">
<value>
/accounts/*=accountsController
</value>
<property>
</bean>
public class AccountsController {
@RequestMapping(method=RequestMethod.GET)
public List<Account> list() {…}
@RequestMapping(method=RequestMethod.GET)
public Account show(@RequestParam("id") Long id) {…}
@RequestMapping(method=RequestMethod.POST)
public String create(Account account) {…}
…
}
Here the controller mapping resides in a central XML-based mapping while the action method mapping is specified through annotations. This approach can be described as a POJO MultiActionController with annotation-based method dispatching. In fact in a recent communication within SpringSource, Juergen pointed out that providing an annotation-based alternative to the MultiActionController was an explicit Spring 2.5 design goal so I guess there are no surprises there! Furthermore, there is work under way to allow you to combine method-level @RequestMapping annotations with the ControllerClassNameHandlerMapping convention (see SPR-4129).
So what's the significance of all this?
An all XML-based approach to configuring the Web layer can get noisy, but centralized, externalized configuration does have its place. Extending from framework-specific base classes with deep inheritance hierarchies to implement control logic can also get noisy, and we generally believe that should be avoided if you can help it. In Spring MVC 2.5, annotations can help address both of these concerns by encapsulating method mapping rules inside your Controller class and also allowing you to implement your Controllers as POJOs. Furthermore, the hybrid approach above shows how you can get the best of externalized configuration and annotation-based configuration.
Adam Sherman says:
Added on March 24th, 2008 at 10:38 amI really like this approach. However, how do we replicate the existing functionality in the abstract controller classes? (AbstractCommandController, etc.)
A.
Rossen Stoyanchev (blog author) says:
Added on March 24th, 2008 at 12:46 pmAdam, a few different annotations can help gain much of the same functionality. For example during a form submit you can get access to the command object populated with a DataBinder by placing the @ModelAttribute("command") annotation on an input parameter. The @SessionAttribute("command") on a class lets you store a model attribute in the session between requests. The @InitBinder annotation placed on a method lets you customize the DataBinder. Take a look at the Petclinic sample in Spring 2.5, which has been updated to use Spring MVC annotations. All of these are shown there.
tzolov says:
Added on March 24th, 2008 at 6:44 pmHi Rosene,
While mixing the traditional Controller sub-class style with the annotation based style I’ve noticed that my traditional controllers were not mapped until I've defied the SimpleControllerHandlerAdapter explicitly in the application context.
So if the AnnotationMethodHandlerAdapter is defined explicitly then all default adapters are implicitly turned off and the traditional (old style) controllers do not work. To solve it I had to define the default handler SimpleControllerHandlerAdapter explicitly.
Cheers, Chris
Juergen Hoeller (blog author) says:
Added on March 27th, 2008 at 5:53 amFYI, the ControllerClassNameHandlerMapping revision for @Controller beans is now available in the latest 2.5.3 snapshots! It autodetects @Controller beans as well now (when running on Java 1.5 or above), mapping the according to the class name strategy.
Juergen
Javier Diaz says:
Added on September 19th, 2008 at 9:52 amHi.
I'm trying to use this approach. I really like how things get organized if you follow this approach.
Some of our controllers (all of then annotated with @Controller) have parameters being passed in the constructor. I'm trying not to use @Autowired as Spring IDE is still not supporting it (as far as I know)
So I'm defining the bean for the controller with the constructor-arg, which works ok, but of course it looks a bit silly to me as I'm defining the same bean twice. See below:
/ltb/*.cgi = ltbController
What I would like to know is what would be the best way to implement the approach you described in this blog if you need inject parameters to your constructor and you don't want to use @Autowired?
Thx
Javier
Javier Diaz says:
Added on September 19th, 2008 at 10:11 amSorry the xml part was missing
[quote post="297"]
/ltb/*.cgi = ltbController
[/quote]
Javier Diaz says:
Added on September 19th, 2008 at 10:44 amSorry, here again
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" >
<property name="mappings">
<value>
/ltb/*.cgi = ltbController
</value>
</property>
</bean>
<bean id="ltbController" class="com.lastminute.lfe.dashboard.web.LtbController">
<constructor-arg ref="jdbcSubagentManager"/>
</bean>
Rossen Stoyanchev (blog author) says:
Added on September 19th, 2008 at 12:31 pmJavier,
There is an SPR mentioned in the posting that was resolved in Spring 2.5.3. Have a look at using ControllerClassNameHandlerMapping instead of SimpleUrlHandlerMapping. The approach works with both @Controller and with XML-based controllers.
For more details see this infoq article:
http://www.infoq.com/articles/spring-2.5-ii-spring-mvc