Après avoir parcouru quelques tutoriels et lu le document initial dans la référence docs.spring.org, j'ai compris qu'il était créé dans le contrôleur d'une classe POJO créée par le développeur. Mais en lisant ceci, je suis tombé sur le paragraphe ci-dessous:
Un
@ModelAttribute
sur un argument de méthode indique que l'argument doit être extrait du modèle. S'il n'est pas présent dans le modèle, l'argument doit d'abord être instancié, puis ajouté au modèle. Une fois présents dans le modèle, les champs de l'argument doivent être renseignés à partir de tous les paramètres de requête ayant des noms correspondants. C'est ce que l'on appelle la liaison de données dans Spring MVC, un mécanisme très utile qui vous évite d'avoir à analyser chaque champ de formulaire individuellement.@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) public String processSubmit(@ModelAttribute Pet pet) { }
Dans le paragraphe ce qui est le plus inquiétant est la ligne:
"Si non présent dans le modèle ..."
Comment les données peuvent-elles être présentes dans le modèle? (Parce que nous n'avons pas créé de modèle - il sera créé par nous.)
De plus, j'ai vu quelques méthodes de contrôleur accepter le type Model
comme argument. Qu'est-ce que ça veut dire? Est-ce que cela crée la Model
créée quelque part? Si oui, qui le crée pour nous?
S'il n'est pas présent dans le modèle, l'argument doit d'abord être instancié, puis ajouté au modèle.
Le paragraphe décrit le code suivant:
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);
(extrait de ModelAttributeMethodProcessor#resolveArgument
)
Pour chaque requête, Spring initialise une ModelAndViewContainer
instance qui enregistre les décisions relatives au modèle et à la vue prises par HandlerMethodArgumentResolver
s et HandlerMethodReturnValueHandler
s au cours de l’appel d’une méthode contrôleur.
Un objet ModelAndViewContainer
nouvellement créé est initialement rempli d'attributs flash (le cas échéant):
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
Cela signifie que l'argument ne sera pas initialisé s'il existe déjà dans le modèle.
Pour le prouver, passons à un exemple concret.
La classe Pet
:
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
}
La classe PetController
:
@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");
}
}
Faisons une demande POST à l'URI /owners/123/pets/456/edit
et voyons les résultats:
A new Pet instance was created!
Pet[456,123,null]
Pet[456,123,XXX]
A new Pet instance was created!
Spring a créé une ModelAndViewContainer
et n'a rien trouvé qui puisse remplir l'instance (c'est une demande d'un client; il n'y a pas eu de redirections). Comme le modèle est vide, Spring a dû créer un nouvel objet Pet
en appelant le constructeur par défaut qui a imprimé la ligne.
Pet[456,123,null]
Une fois présents dans le modèle, les champs de l'argument doivent être renseignés à partir de tous les paramètres de requête ayant des noms correspondants.
Nous avons imprimé la variable Pet
donnée pour nous assurer que tous les champs petId
et ownerId
avaient été liés correctement.
Pet[456,123,XXX]
Nous paramétrons hiddenField
pour vérifier notre théorie et redirigé vers la méthode invokeInternal
qui attend également un @ModelAttribute
. Comme nous le voyons, la deuxième méthode a reçu l'instance (avec sa propre valeur cachée) qui a été créée pour la première méthode.
Pour répondre à la question, j'ai trouvé quelques extraits de code à l'aide de @andrew answer. Laquelle justifie une instance ModelMap [un objet de modèle] est créée bien avant que notre contrôleur/gestionnaire soit appelé pour une URL spécifique
public class ModelAndViewContainer {
private boolean ignoreDefaultModelOnRedirect = false;
@Nullable
private Object view;
private final ModelMap defaultModel = new BindingAwareModelMap();
....
.....
}
Si nous voyons le code d'extrait ci-dessus (tiré de spring-webmvc-5.0.8 jar). BindingAwareModelMap l'objet modèle est créé bien avant.
Pour mieux comprendre l'ajout des commentaires pour la classe BindingAwareModelMap
/**
* Subclass of {@link org.springframework.ui.ExtendedModelMap} that automatically removes
* a {@link org.springframework.validation.BindingResult} object if the corresponding
* target attribute gets replaced through regular {@link Map} operations.
*
* <p>This is the class exposed to handler methods by Spring MVC, typically consumed through
* a declaration of the {@link org.springframework.ui.Model} interface. There is no need to
* build it within user code; a plain {@link org.springframework.ui.ModelMap} or even a just
* a regular {@link Map} with String keys will be good enough to return a user model.
*
@SuppressWarnings("serial")
public class BindingAwareModelMap extends ExtendedModelMap {
....
....
}