If not present in the model, the argument should be instantiated first and then added to the model.
The paragraph describes the following piece of code:
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
} else {
// Create attribute instance
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (BindException ex) {
...
}
}
...
mavContainer.addAllAttributes(attribute);
(taken from ModelAttributeMethodProcessor#resolveArgument
)
For every request, Spring initialises a ModelAndViewContainer
instance which records model and view-related decisions made by HandlerMethodArgumentResolver
s and HandlerMethodReturnValueHandler
s during the course of invocation of a controller method.
A newly-created ModelAndViewContainer
object is initially populated with flash attributes (if any):
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
It means that the argument won't be initialised if it already exists in the model.
To prove it, let's move to a practical example.
The Pet
class:
public class Pet {
private String petId;
private String ownerId;
private String hiddenField;
public Pet() {
System.out.println("A new Pet instance was created!");
}
// setters and toString
}
The PetController
class:
@RestController
public class PetController {
@GetMapping(value = "/internal")
public void invokeInternal(@ModelAttribute Pet pet) {
System.out.println(pet);
}
@PostMapping(value = "/owners/{ownerId}/pets/{petId}/edit")
public RedirectView editPet(@ModelAttribute Pet pet, RedirectAttributes attributes) {
System.out.println(pet);
pet.setHiddenField("XXX");
attributes.addFlashAttribute("pet", pet);
return new RedirectView("/internal");
}
}
Let's make a POST request to the URI /owners/123/pets/456/edit
and see the results:
A new Pet instance was created!
Pet[456,123,null]
Pet[456,123,XXX]
A new Pet instance was created!
Spring created a ModelAndViewContainer
and didn't find anything to fill the instance with (it's a request from a client; there weren't any redirects). Since the model is empty, Spring had to create a new Pet
object by invoking the default constructor which printed the line.
Pet[456,123,null]
Once present in the model, the argument's fields should be populated from all request parameters that have matching names.
We printed the given Pet
to make sure all the fields petId
and ownerId
had been bound correctly.
Pet[456,123,XXX]
We set hiddenField
to check our theory and redirected to the method invokeInternal
which also expects a @ModelAttribute
. As we see, the second method received the instance (with own hidden value) which was created for the first method.