From 1fd8f7798996ed8a30534c9ec3e66b36a9cbac2f Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 27 Jun 2011 21:12:58 +0000 Subject: [PATCH] SPR-7608 Add several sections to the reference docs on working with the @ModelAttribute annotation. Specifically the goal was to cover the abitlity for an @ModelAttribute argument to be instantiated from a URI template variable with the help of a Converter or a PropertyEditor. --- spring-framework-reference/src/mvc.xml | 284 ++++++++++++++++++------- 1 file changed, 207 insertions(+), 77 deletions(-) diff --git a/spring-framework-reference/src/mvc.xml b/spring-framework-reference/src/mvc.xml index 4a60c74079d..001ff47a3dd 100644 --- a/spring-framework-reference/src/mvc.xml +++ b/spring-framework-reference/src/mvc.xml @@ -1468,95 +1468,225 @@ public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntit linkend="rest-message-conversion">Message Converters. -
- Command and Form Objects - - The @ModelAttribute annotation is central to - working with command and form objects. It has a couple of usage scenarios - described in this section. - +
+ Using <interfacename>@ModelAttribute</interfacename> on a controller method - The main scenario is using @ModelAttribute on - a method parameter in order to get access to data received from a form submission - or from request parameters. For example an object of type - Person with fields firstName - and lastName will be populated accordingly assuming - the presence of either form or query string parameters with matching names: - e.g. firstName=Rod and lastName=Johnson. - Below is an example of a @ModelAttribute-annotated - method parameter. + The @ModelAttribute annotation can be used on a + method or on a method argument. This section explains its usage on a method while the + next section explains its usage on a method argument. - @Controller -@RequestMapping("/owners/{ownerId}/pets/{petId}/edit") -@SessionAttributes("pet") -public class EditPetForm { - - @RequestMapping(method = RequestMethod.POST) - public String processSubmit( - @ModelAttribute("pet") Pet pet, - BindingResult result, SessionStatus status) { - - } - -} - - Before invoking the method, Spring MVC will create a Pet - instance, populate it using request parameters, and also add it to the model - under the name pet. - The Pet instance may have been created using the - default constructor (if available), it may have been obtained from the HTTP session in - conjunction with use of @SessionAttributes (see the next section), or - it may have been created by another @ModelAttribute-annotated method - in the same class. A @ModelAttribute-annotated method - is the second scenario for using the annotation. + An @ModelAttribute on a method indicates a method + for adding model attributes. A few examples: - When used at the method level a @ModelAttribute - contributes one or more objects to the model. See the populatePetTypes() - method in the following example: + @ModelAttribute +public void populateModel(Model model) { + model.addAttribute("types", this.clinic.getPetTypes(); + // add more attributes ... +} - @Controller -@RequestMapping("/owners/{ownerId}/pets/{petId}/edit") -@SessionAttributes("pet") -public class EditPetForm { +@ModelAttribute +public void addAccount(@RequestParam String number, Model model) { + Account account = accountManager.findAccount(number); + model.addAttribute(account); +} - // ... +@ModelAttribute +public Account addAccount(@RequestParam String number) { + return accountManager.findAccount(number); +} + - @ModelAttribute("types") - public Collection<PetType> populatePetTypes() { - return this.clinic.getPetTypes(); - } + @ModelAttribute methods are not associated + with any request mappings. Simply they are invoked prior + to the invocation of every + @RequestMapping method in the same controller. + In terms of method arguments they support the same argument types as + @RequestMapping methods, hence allowing access to request + parameters, path variables, and so on -- see + for a complete listing. - @RequestMapping(method = RequestMethod.POST) - public String processSubmit( - @ModelAttribute("pet") Pet pet, - BindingResult result, SessionStatus status) { + There are two common use cases for @ModelAttribute + methods. One, is to add commonly needed model attributes - see the first example + above where the model is populated with pet types to be shown in a drop-down. + The second use case is to pre-load a command object - see the second and third + examples above with the Account object (more on command objects in the next section). - new PetValidator().validate(pet, result); - if (result.hasErrors()) { - return "petForm"; - } - else { - this.clinic.storePet(pet); - status.setComplete(); - return "redirect:owner.do?ownerId=" + pet.getOwner().getId(); - } - } + Note the two styles of @ModelAttribute methods. + In one style, the method accepts a Model and adds any number + of model attributes to it. In the second style, the method adds an attribute implicitly + by having it as its return value. That results in the object being added as an + attribute to the model. You can choose between the two styles depending on + whether you need to add one model attribute or multiple. -} - - @ModelAttribute methods are - executed before the chosen - @RequestMapping annotated handler method. - They effectively pre-populate the model with specific - attributes, often loaded from a database. Such an attribute can then - be accessed through a @ModelAttribute-annotated - @RequestMapping parameter. - An @ModelAttribute method can contain the same - method arguments as documented previously for - @RequestMapping methods. + A controller can have any number of @ModelAttribute + methods. All such methods are invoked before @RequestMapping + methods within the same controller each contributing model attributes. + + What happens when a model attribute name is not explicitly provided? In such cases + a default name is assigned to the model attribute. + For example if the method returns an object of type Account, + the default name used is "account". However, you can also change it to something else + through the value of the @ModelAttribute annotation. + Or if adding attributes directly to the Model, use the + appropriate overloaded addAttribute(..) method providing + both the attribute name and the attribute value. + Lastly the @ModelAttribute annotation can also + be used directly on an @RequestMapping method. In this + cause, the return value of the @RequestMapping method + will be added as a model attribute and the view name will be derived using + a convention -- see . +
+
+ Using <interfacename>@ModelAttribute</interfacename> on a controller method argument + + As explained in the previous section an @ModelAttribute + annotation can be used on a method or on a method argument. When used on a method argument, + the @ModelAttribute annotation indicates a command object. A command + object is an object with properties (e.g. Account rather than + a simple type like int or String) that is used to + populate a form or reversely to be populated with the data from an HTML form. + As you will see command objects can significantly automate the process of + working with forms -- rather than dealing with individual form + parameters one at a time you'll be have them in a one command object. + + To be more precise annotating a method argument with + @ModelAttribute means 3 things: + + The command object should be located and/or instantiated. + It should be populated with request parameters from the current request. + The object should be added to the model. + + See the following example: + + + +@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) +public String processSubmit(@ModelAttribute("pet") Pet pet) { + +} + + The @ModelAttribute annotation + designates "pet" as a command object. But where does the object come from? There are two options. + It may have been added to the model by an @ModelAttribute + method as explained in the previous section. Or otherwise if it's not already in the model, it + is instantiated through the default constructor. Typically if the object needs to be retrieved + from a database first, you can create an @ModelAttribute + method to do that. Or otherwise rely on the default constructor. If multiple methods in the + controller work on the same object, you can have it stored in the HTTP session between requests. + That is explained in . + + + Using a URI template variable to retrieve a command object + + There is one other more advanced way of instantiating a command object. + It is useful when the command object can be retrieved based on a URI template + variable. Here is an example: + + +@RequestMapping(value="/accounts/{account}", method = RequestMethod.PUT) +public String save(@ModelAttribute("account") Account account) { + +} + The name of the model attribute "account" matches to the name of a URI + template variable. Assuming there is a registered + Converter<String, Account> + or PropertyEditor that can turn a String + account number into an Account instance, those will be used + to provision the command object and have it added to the model. + If such a Converter or PropertyEditor does not exist, however the default + constructor will still be used. + + +
+ +
+ Data binding and validation with <interfacename>@ModelAttribute</interfacename> + + As mentioned in the previous section an @ModelAttribute + annotation means 3 things: + + The command object should be located and/or instantiated. + It should be populated with request parameters from the current request. + The object should be added to the model. + + In this section we'll focus on the second: data binding and validation. + + After the command object has been provisioned using one of the methods described in the + previous section, the next step is to apply data binding and validation. + Spring's WebDataBinder does that by matching request parameters -- + usually form data fields but can also be query string parameters -- to command object + properties, including properties of nested objects, and populates those properties + accordingly also applying type conversion as necessary. + For example an object of type Person with + properties firstName and age + will be populated assuming the presence of such named form fields. + As a result of data binding the command object is populated with the form data. + + There are various ways to customize the data binding process. + For example you may wish to specify required fields, allowed and disallowed + fields, or extend type conversion. + See and also . + + As a result of data binding there may be errors such as missing required + fields or type conversion errors. To check for such errors + add a BindingResult argument immediately following + the @ModelAttribute argment: + + +@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) +public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { + + if (result.hasErrors()) { + return "petForm"; + } + + // ... + +} + + BindingResult allows you to check if errors were found in + which case it's common to return to the same form where the errors + can be rendered with the help of Spring's <errors> form tag. + + In addition to data binding you can apply validation using your own custom + validator passing the same BindingResult that was used to record + data binding errors. That allows for data binding and validation errors to be accumulated + in one place and subsequently reported back to the user: + + +@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) +public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { + + new PetValidator().validate(pet, result); + if (result.hasErrors()) { + return "petForm"; + } + + // ... +} + + Or you can have validation invoked automatically by adding the + JSR-303 @Valid annotation: + + +@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) +public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { + + if (result.hasErrors()) { + return "petForm"; + } + + // ... +} + + See and + for details on how to configure and use Spring's Validation support. + +
+
Specifying attributes to store in a session with <classname>@SessionAttributes</classname>