// Add one attribute
// The return value of the method is added to the model under the name "account"
// You can customize the name via @ModelAttribute("myAccount")
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountManager.findAccount(number);
}
// Add multiple attributes
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountManager.findAccount(number));
// add more ...
}
@ModelAttribute
Using @ModelAttribute on a method
The @ModelAttribute
annotation can be used on methods or on method arguments. This
section explains its usage on methods while the next section explains its usage on
method arguments.
An @ModelAttribute
on a method indicates the purpose of that method is to add one or
more model attributes. Such methods support the same argument types as @RequestMapping
methods but cannot be mapped directly to requests. Instead @ModelAttribute
methods in
a controller are invoked before @RequestMapping
methods, within the same controller. A
couple of examples:
@ModelAttribute
methods are used to populate the model with commonly needed attributes
for example to fill a drop-down with states or with pet types, or to retrieve a command
object like Account in order to use it to represent the data on an HTML form. The latter
case is further discussed in the next section.
Note the two styles of @ModelAttribute
methods. In the first, the method adds an
attribute implicitly by returning it. In the second, the method accepts a Model
and
adds any number of model attributes to it. You can choose between the two styles
depending on your needs.
A controller can have any number of @ModelAttribute
methods. All such methods are
invoked before @RequestMapping
methods of the same controller.
@ModelAttribute
methods can also be defined in an @ControllerAdvice
-annotated class
and such methods apply to many controllers. See the [mvc-ann-controller-advice] section
for more details.
Tip
|
What happens when a model attribute name is not explicitly specified? In such cases a
default name is assigned to the model attribute based on its type. For example if the
method returns an object of type |
The @ModelAttribute
annotation can be used on @RequestMapping
methods as well. In
that case the return value of the @RequestMapping
method is interpreted as a model
attribute rather than as a view name. The view name is then derived based on view name
conventions instead, much like for methods returning void
.
Using @ModelAttribute on a method argument
As explained in the previous section @ModelAttribute
can be used on methods or on
method arguments. This section explains its usage on method arguments.
An @ModelAttribute
on a method argument indicates the argument should be retrieved
from the model. If not present in the model, the argument should be instantiated first
and then added to the model. Once present in the model, the argument’s fields should be
populated from all request parameters that have matching names. This is known as data
binding in Spring MVC, a very useful mechanism that saves you from having to parse each
form field individually.
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }
Given the above example where can the Pet instance come from? There are several options:
-
It may already be in the model due to use of
@SessionAttributes
— see [mvc-ann-sessionattrib]. -
It may already be in the model due to an
@ModelAttribute
method in the same controller — as explained in the previous section. -
It may be retrieved based on a URI template variable and type converter (explained in more detail below).
-
It may be instantiated using its default constructor.
An @ModelAttribute
method is a common way to retrieve an attribute from the
database, which may optionally be stored between requests through the use of
@SessionAttributes
. In some cases it may be convenient to retrieve the attribute by
using an URI template variable and a type converter. Here is an example:
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
// ...
}
In this example the name of the model attribute (i.e. "account") matches the name of a
URI template variable. If you register Converter<String, Account>
that can turn the
String
account value into an Account
instance, then the above example will work
without the need for an @ModelAttribute
method.
The next step is data binding. The WebDataBinder
class matches request parameter names — including query string parameters and form fields — to model attribute fields by
name. Matching fields are populated after type conversion (from String to the target
field type) has been applied where necessary. Data binding and validation are covered in
[validation]. Customizing the data binding process for a controller level is covered
in [mvc-ann-webdatabinder].
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
argument:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
With a BindingResult
you can check if errors were found in which case it’s common to
render the same form where the errors can be shown with the help of Spring’s <errors>
form tag.
Note that in some cases it may be useful to gain access to an attribute in the
model without data binding. For such cases you may inject the Model
into the
controller or alternatively use the binding
flag on the annotation:
@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
@ModelAttribute(binding=false) Account account) {
// ...
}
In addition to data binding you can also invoke 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:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
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:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}