lishman levelup
«previous  next»


Getting Started
Form Processing
IoC Container
Hibernate ORM



The Spring MVC Form Controller


A new controller class is introduced to handle the various form processing operations. Methods on the controller are responsible for creating, updating and deleting countries, and for validating any new or changed data.

Request Mapping

First, let's take a look at the overall structure of the class, with regards to request mapping.
@Controller
@RequestMapping("/countryForm.html")
@SessionAttributes("country")
public class CountryForm {

  @RequestMapping(method = RequestMethod.GET)
  public Country setUpForm(@RequestParam(value="id", required = false) Integer countryId) {
    ..
  }

  @RequestMapping(params = "create", method = RequestMethod.POST)
  public String create(Country country, BindingResult result, SessionStatus status) {
    ..
  }

  @RequestMapping(params = "update", method = RequestMethod.POST)
  public String update(Country country, BindingResult result, SessionStatus status) {
    ..
  }

  @RequestMapping(params = "delete", method = RequestMethod.POST)
  public String delete(Country country, BindingResult result, SessionStatus status) {
    ..
  }
}
This time, we annotate the controller class itself with @RequestMapping, to map the /countryForm.html URL path. Then, we add further @RequestMapping annotations to target a particular method, based on the type of request.

For example, an HTTP GET request to the /countryForm.html path will invoke the setUpForm() method, which returns a form backing object. POST methods, on the other hand, will call the appropriate method depending on which button is pressed on the web page. The params element of the @RequestMapping annotation checks for the existence of a request parameter which, in this case, is the name attribute of the HTML <button> element, which submitted the form.

Methods annotated with @RequestMapping have very flexible method signatures. Spring's data binding features automatically populate the country parameter from the data submitted in the form. BindingResult is used for form validation, and SessionStatus is discussed below.

Form Backing Object

The form backing object is the object that is used to populate the form with data. When the controller receives an HTTP GET request, the setUpForm() method is invoked and a Country object is returned into the model.
@RequestMapping(method = RequestMethod.GET)
public Country setUpForm(@RequestParam(value="id", required = false) Integer countryId) {
  if (countryId == null) {
    return new Country();
  } else {
    return worldService.getCountryById(countryId);
  }
}
If the 'id' request parameter has a value, then the request is for an update or a delete operation, so a populated Country object must be provided. If the 'id' parameter is not supplied, then the request is part of a create operation, and an empty Country object is returned.

So, as we will see in the next section, countryForm.html is called from countryList.html without any request parameters when the Create button is clicked, and called with an 'id' parameter (eg. countryForm.html?id=99) when one of the edit buttons is clicked.

Saving the Data

The create and update operations do the same thing. They both receive data from the form, validate it (more on this later) and then use the WorldService business object to save the information.
@RequestMapping(params = "create", method = RequestMethod.POST)
public String create(Country country, BindingResult result, SessionStatus status) {
  return update(country, result, status);
}

@RequestMapping(params = "update", method = RequestMethod.POST)
public String update(Country country, BindingResult result, SessionStatus status) {
  countryValidator.validate(country, result);
  if (result.hasErrors()) {
    return "countryForm";
  } else {
    worldService.saveCountry(country);
    status.setComplete();
    return "redirect:countryList.html";
  }
}
There is no way to associate a single method with multiple request mappings, so we simply call one method from the other.

Tip
Tip - another solution would be to use the same value (eg 'save') in the name atribute of the Create and Update <button> elements in the JSP code, and then check for this value in @RequestMapping params.

Deleting a Country

The delete() method is called when the Delete button has been pressed and the user has confirmed that the request is to go ahead.
@RequestMapping(params = "delete", method = RequestMethod.POST)
public String delete(Country country, BindingResult result, SessionStatus status) {
  worldService.deleteCountry(country);
  status.setComplete();
  return "redirect:countryList.html";
}

Returning Logical View Names

You will have noticed that some of the methods return a String value. When a String is returned from a method annotated with @RequestMapping, it represents a logical view name. In other words, rather than letting Spring determine the view name from the URL, we are telling Spring exactly which page to display next.

The create() and update() methods take the user back to the /countryForm.jsp page if any validation errors are detected. If the user's input is valid, then the /countryList.jsp page is displayed instead.

The redirect: prefix triggers an HTTP redirect back to the browser. This is necessary when delegating the response to another controller, rather than just rendering the view. In this example, when valid data has been submitted from the form, we take the user to the country list page. Redirection ensures that the getCountries() method is called on the CountryController class, and the necessary data is added to the model. Without the redirect: prefix, Spring would render the view with no data.

Session Attributes

By default, the Country object returned by setUpForm() is not the same object as the one passed into the create(), update() or delete() methods, when the form is submitted. Therefore, only data from the form (which doesn't include all of the properties in our backing object) will be bound to the Country objects in these methods.

More often than not, what we really need is to access the original object (the one populated in the setupForm() method) plus any changes made to this data on the form. In particular, we need the object to contain the original identifier value, so that we can determine which Country object is to be updated or deleted.

The type level @SessionAttributes annotation does just this by specifying the objects, by name or by type, to be kept in session between requests. The setComplete() method on the SessionStatus class, marks session processing as complete, and allows the session attributes to be cleaned up.
»