Je me suis parfois demandé ce qui était arrivé au processus de liaison d'un paramètre de requête en utilisant @ ModelAttribute
à un objet, je vais donc résumer les résultats.
Il n'est souvent pas mentionné dans la documentation et les résultats de la lecture du code source sont résumés.
Vous pouvez également obtenir des paramètres de requête avec @ RequestParam
, mais cette fois, c'est hors de portée.
@ ModelAttribute
peut être utilisé--Méthode
En fait, il n'est pas nécessaire d'ajouter @ ModelAttribute
à l'argument de la méthode Handler.
Dans le comportement par défaut, si vous spécifiez une classe jugée «false» dans «BeanUtils.isSimpleProperty» dans l'argument de la méthode Handler, le comportement est le même que lorsque «@ ModelAttribute» est ajouté.
Plus précisément, à part les types primitifs et leurs wrappers, ʻEnum,
String,
CharSequence,
Number,
Date, ʻURI
, ʻURL,
Locale,
Class, arrays, etc. une sorte de. Cependant, il a la priorité la plus basse, donc si un autre
HandlerMethodArgumentResolver` peut résoudre l'argument, il y sera résolu.
ModelAttributeMethodProcessor
Dans Spring, HandlerAdapter
appelle la méthode Handler. Il existe plusieurs classes d'implémentation, mais si vous utilisez @ RequestMapiing
, RequestMappingHandlerAdapter
est responsable de l'appel de la méthode Handler.
C'est cette classe qui résout la valeur passée à l'argument de la méthode Handler et gère la valeur de retour, et il est agréable de spécifier Model
ou RedirectAttributes
comme argument de la méthode Handler. Travaille dur.
Donc, c'est le rôle de la classe d'implémentation de HandlerMethodArgumentResolver
de résoudre réellement la valeur passée à l'argument, et quand @ ModelAttribute
est spécifié, ModelAttributeMethodProcessor
est en train de traiter.
@ ModelAttribute
est ajouté à la méthodeCe n'était pas le sujet principal, mais je résumerai également le fonctionnement de la méthode avec @ ModelAttribute
.
Le processus de stockage de la valeur de retour de la méthode dans Model
est effectué avant d'exécuter la méthode Handler.
(Strictement parlant, il s'agit de ModelMap
dans ModelAndViewContainer
, mais cela semble déroutant, donc je l'appellerai Model
.)
Par exemple, supposons que vous créez le contrôleur suivant.
@Controller
public class HelloController {}
@ModelAttribute
public User setUp() {
return new User();
}
@GetMapping("/")
public String index() {
return "hello";
}
}
Le comportement à ce moment-là est le suivant. (À proprement parler, ce n'est qu'une image.)
@Controller
public class HelloController {
@GetMapping("/")
public String index(Model model) {
model.addAttribute(new User());
return "hello";
}
}
Lorsque le nom de l'attribut est spécifié dans @ ModelAttriubte
comme indiqué ci-dessous,
@ModelAttribute("hoge")
public User setUp() {
return new User();
}
L'image ajoutée au premier argument de model.addAttribute ()
.
@GetMapping("/")
public String index(Model model) {
model.addAttribute("hoge", new User());
return "index";
}
Au fait, c'est la classe appelée ModelFactory
qui appelle la méthode avec @ ModelAttribute
.
@ ModelAttribute
est ajouté à l'argument de la méthode HandlerLe sujet principal est d'ici.
Tout d'abord, récupérez l'objet spécifié à partir de Model
. Si «valeur» ou «nom» est spécifié pour «@ ModelAttribute», la clé d'obtention de «Model» peut être spécifiée. S'il est omis, Spring est automatiquement déterminé à partir du nom de la classe.
Il existe plusieurs modèles qui peuvent être obtenus à partir de Model
, tels que le stockage dans Model
avec la méthode avec @ ModelAttribute
mentionnée ci-dessus, en utilisant la portée Flash au moment de la redirection ou en définissant la portée de la session avec @ SessionAttribute
. Si vous l'utilisez, il peut être stocké dans Model
avant d'exécuter la méthode Handler (ou plutôt avant de résoudre la valeur passée à l'argument).
Si l'objet ne peut pas être obtenu à partir de «Model», un objet est créé. S'il y a un constructeur, il sera utilisé, s'il y a plusieurs constructeurs, le constructeur par défaut sera utilisé, mais s'il n'y a pas de constructeur par défaut à ce moment, il mourra. Lors de l'utilisation d'un constructeur avec des arguments, le processus de liaison des paramètres de demande est inséré, ce qui sera décrit plus loin.
Liez la valeur du paramètre de demande à l'objet acquis ou généré. Une valeur est liée au champ de l'objet qui correspond au nom du paramètre de demande.
Remplace la valeur de l'objet, mais ne fait rien pour les valeurs non incluses dans les paramètres de la demande.
@Controller
public class HelloController {
@ModelAttribute
public User setUp() {
User user = new User();
user.setName("Nom");
user.setEmail("Email");
return user;
}
@GetMapping("/")
public String index(User user) {
return "hello";
}
}
Supposons que ʻUser ait des champs
nom et ʻemail
.
Ensuite, comme indiqué ci-dessous, seul «nom» est ajouté au paramètre de requête et envoyé.
curl http://localhost:8080?name=hogehoge
Le nom
de l'objet ʻUser devient
hogehoge, et ʻemail
devient mail
, qui n'est pas écrasé par null
.
Lors de la création d'un objet à l'aide d'un constructeur avec des paramètres, essayez de lier les données à ce moment. Dans ce cas, liez le paramètre de demande qui correspond à la valeur spécifiée dans le nom du paramètre du constructeur ou «@ ConstructorProperties» au lieu du nom du champ.
public class User {
private String name;
private String email;
public User(String n, String e) {
this.name = n;
this.email = e;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
Dans le cas du constructeur ci-dessus, le nom du paramètre de requête doit être «n» ou «e».
public class User {
private String name;
private String email;
@ConstructorProperties({"name", "email"})
public User(String n, String e) {
this.name = n;
this.email = e;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
Si vous utilisez @ ConstructorProperties
comme ci-dessus, le nom du paramètre de requête peut être name
ou ʻemail. S'il y a
@ ConstructorProperties, il sera prioritaire, donc il ne fonctionnera pas avec
n ou ʻe
.
Cependant, une fois la création de l'objet terminée, le même processus de liaison du paramètre de requête que d'habitude est exécuté, donc s'il y a un setter ou si vous utilisez DataBinder
qui permet un accès direct au champ, cela Le résultat de est prioritaire.
Par exemple, si vous créez la classe suivante
public class User {
private String name;
private String email;
public User(String name, String email) {
this.name = name + "hoge";
this.email = email + "fuga";
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public void setName(String name) {
this.name = name;
}
public void setEmail(String email) {
this.email = email;
}
}
Dans le constructeur, «hoge» et «fuga» sont donnés et conservés dans le champ, mais comme setter est exécuté après cela, «hoge» et «fuga» sont attribués à la valeur pour être finalement liés. Cela n'a pas été le cas.
En changeant binding
of @ ModelAttribute
en false
, il est possible de supprimer la liaison des paramètres de requête.
@Controller
public class HelloController {
@ModelAttribute
public User setUp() {
User user = new User();
user.setName("Nom");
user.setEmail("Email");
return user;
}
@GetMapping("/")
public String index(@ModelAttribute(binding = false) User user) {
return "hello";
}
}
Même si la requête suivante est envoyée dans l'état ci-dessus, le "nom" de l'objet de ʻUser reste
nom, et ʻemail
reste mail
.
curl http://localhost:8080?name=hogehoge&email=fugafuga
Effectuez la validation lorsqu'une validation est requise, par exemple lorsque «@ Validated» est ajouté à l'argument.
À la suite de la validation, s'il y a une erreur et qu'il n'y a pas d'erreurs dans l'argument immédiatement après la méthode Handler, BindingException
est levée, et si c'est le cas, le résultat est conservé comme BindingResult
. (ʻErrors est l'interface parente de
BindingResult`)
Au fait, lorsque vous définissez BindingResult
comme argument dans la méthode Handler, vous devez faire attention dans l'ordre à cause de l'implémentation ici.
L'histoire tourne mal, mais si vous définissez BindingResult
sur l'argument de la méthode Handler, la classe ʻErrorsMethodArgumentResolverrésout la valeur de l'argument. Cette classe vérifie si le dernier élément stocké dans
Model est
BindingResult, et si c'est le cas, le définit dans l'argument. Par conséquent, il peut ne pas fonctionner correctement à moins qu'il ne soit exécuté immédiatement après avoir effectué la validation et le stockage de
BindingResult dans
Model. Par conséquent, il semble que l'argument de la méthode Handler doit avoir
BindingResult immédiatement après l'argument avec
@ Validated`.
Après avoir étudié cela, j'ai pensé qu'il était possible de réaliser un code qui divise l'objet qui lie le paramètre de requête en deux et obtient le résultat de validation de chacun comme suit.
Le résultat de validation de ʻUser est stocké dans
result1, et le résultat de validation de ʻOtherObj
est stocké dans result2
.
@Controller
public class HelloController {
@GetMapping("/")
public String index(@Validated User user, BindingResult result1, @Validated OtherObj other, BindingResult result2) {
return "index";
}
}
Lors de l'utilisation d'un constructeur avec des paramètres, la conversion de type est effectuée si nécessaire au moment de la création de l'objet. À ce moment-là, si un champ qui ne peut pas être converti de type est inclus, le résultat est conservé comme «BindingResult», mais le traitement de liaison et la validation ultérieurs ne sont pas effectués. En d'autres termes, le traitement de liaison de paramètre de demande par l'installateur et la vérification de champ par Bean Validation ne sont pas effectués.
public class User {
@NotEmpty
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
//getter omis
}
@Controller
public class HelloController {
@GetMapping("/")
public String index(@Validated User user, BindingResult result) {
return "index";
}
}
Lorsque vous créez une classe comme celle ci-dessus, envoyez la demande suivante.
curl http://localhost:8080?age=hogehoge
Le résultat de validation initialement attendu est que «nom» est vide et «âge» ne peut pas être converti en «entier», mais seul ce dernier peut être détecté. Les deux peuvent être détectés en supprimant le constructeur paramétré et en le modifiant pour utiliser le constructeur et le setter par défaut.
Stockez l'objet créé par le processus ci-dessus et «BindingResult» dans «Model».
Si le nom de l'attribut est spécifié dans @ ModelAttribute
, utilisez cette valeur comme clé.
Je vois. Lorsque vous utilisez un constructeur avec des paramètres, vous devez être prudent car l'opération peut être légèrement différente.
Recommended Posts