(Recovered from my old Blog).
As of Spring 2.5, annotations can be used on Spring MVC classes instead of defining each and every page in the *-servlet.xml definition file. However, most online guides still reference the old practice. Below is a simple guide to migrating to (in my opinion), the much cleaner annotation approach.
‘Normal’ Pages
Previously, ‘normal’ pages would:
- Implement the Controller interface
- Implement the handleRequest method
- Have a simple *-servlet entry along the lines of:
1 |
<bean name=“/contact_success.html” class=“com.package.contact.ContactSuccessController” /> |
Switching this to annotations is simple:
Firstly, add the context:component-scan element to your *-servlet.xml file, along with the Spring MVC annotation handlers:
1 2 3 |
<context:component–scan base–package=“com.yourpackage.name” /> <bean class=“org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping” /> <bean class=“org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter” /> |
For the above to work you will also need to add the context namespace URI to your main beans definition (third line down) and the schema to use (6th and 7th lines down):
1 2 3 4 5 6 7 |
<beans xmlns=“http://www.springframework.org/schema/beans” xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:context=“http://www.springframework.org/schema/context” xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd”> |
Annotate your class with the @Controller annotation, and a @RequestMapping annotation which specifies which URL is mapped by this controller:
1 2 3 4 5 |
@Controller @RequestMapping(“/contact_success.html”) public class ContactSuccessController { // Rest of class } |
The method which handles the request (which – unlike the previous case where we implemented an interface – can have any name) should be annotated with @RequestMapping(method = RequestMethod.GET).
So the bean definition in *-servlet.xml is removed, and the class becomes:
1 2 3 4 5 6 7 8 |
@Controller @RequestMapping(“/contact_success.html”) public class ContactSuccessController { @RequestMapping(method = RequestMethod.GET) public ModelAndView handleRequest() { return new ModelAndView(“contact/contactSuccess”); } } |
Note that unlike the traditional xml based approach, the @RequestMapping annotation specifying which URL to map can be set at the method level (allowing multiple URLs to be mapped by a single Controller).
‘Form’ Pages
Form pages are a little more complicated. Previously, they would have:
- Extending the superclass SimpleFormController
- Overridden methods onSubmit and formBackingObject
- Had a complicated bean definition containing all form options (e.g. which validator to use, which success page, etc)
To migrate these, first enable context:component-scan, along with the new Spring MVC annotation bean definitions as per the previous case.
Next, annotate the form class as per the ‘normal’ case. The method annotated with @RequestMapping(method = RequestMethod.GET) handles the initial ’setup’ of the form – basically what was previously handled in formBackingObject. This method also returns the view to use for the form. The command object will be initialised here, then stored in the model).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Controller @RequestMapping(“/contact.html”) public class ContactFormController { @RequestMapping(method = RequestMethod.GET) public ModelAndView setupForm(ModelMap model) { ContactForm contactForm = new ContactForm(); model.addAttribute(“contactForm”, contactForm); return new ModelAndView(“contact/contactForm”); } // CLASS NOT COMPLETE YET!!! } |
The method which is called when the ‘Submit’ button is pressed is annotated in a similar manner, except the RequestMethod is POST instead of GET. However, the differences then get larger:
There is no automated validation (assuming a validator is specified of course). See below how this is handled.
The validator will return a status. If there is an error we simply return the same view. If not, we set the SessionStatus to complete and return the name of the Success view to use.
To get at our command object we need to pull it out of the model – using the @ModelAttribute(“contactForm”) annotation.
As mentioned above, we need to call the validator directly. To do this, we need to @Autowire the validator into the class (as per normal Spring injection). Since we are no longer relying on Spring MVC calling the validator, we can improve the validate method on the validator to become typesafe, instead of passing in a generic object as before.
Complete class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
@Controller @RequestMapping(“/contact.html”) public class ContactFormController { @Autowired ContactFormValidator contactFormValidator; @RequestMapping(method = RequestMethod.GET) public ModelAndView setupForm(ModelMap model) { ContactForm contactForm = new ContactForm(); model.addAttribute(“contactForm”, contactForm); return new ModelAndView(“contact/contactForm”); } @RequestMapping(method = RequestMethod.POST) public ModelAndView onSubmit(@ModelAttribute(“contactForm”) ContactForm contactForm, BindingResult errors, SessionStatus sessionStatus) { contactFormValidator.validate(contactForm, errors); if (errors.hasErrors()) { return new ModelAndView(“contact/contactForm”); } else { // Business Logic sessionStatus.setComplete(); return new ModelAndView(new RedirectView(“contact_success.html”)); } } } |