Ich habe mich manchmal gefragt, was mit dem Prozess des Bindens eines Anforderungsparameters mit @ ModelAttribute
an ein Objekt passiert ist, also werde ich die Ergebnisse zusammenfassen.
Es wird oft nicht in der Dokumentation erwähnt und die Ergebnisse des Lesens des Quellcodes werden zusammengefasst.
Sie können Anforderungsparameter auch mit @ RequestParam
abrufen, diesmal jedoch außerhalb des Gültigkeitsbereichs.
@ ModelAttribute
verwendet werden kann--Methode --Handler-Methodenargumente
Tatsächlich ist es nicht erforderlich, dem Argument der Handler-Methode "@ ModelAttribute" hinzuzufügen. Wenn Sie im Standardverhalten im Argument der Handler-Methode eine Klasse angeben, die in BeanUtils.isSimpleProperty als "false" bewertet wird, ist das Verhalten dasselbe wie beim Hinzufügen von "@ ModelAttribute". Insbesondere außer primitiven Typen und ihren Wrappern, "Enum", "String", "CharSequence", "Number", "Date", "URI", "URL", "Locale", "Class", Arrays usw. Klasse von. Es hat jedoch die niedrigste Priorität. Wenn also ein anderer "HandlerMethodArgumentResolver" das Argument auflösen kann, wird es dort aufgelöst.
ModelAttributeMethodProcessor
Im Frühjahr ruft HandlerAdapter
die Handler-Methode auf. Es gibt mehrere Implementierungsklassen, aber wenn Sie "@ RequestMapiing" verwenden, ist "RequestMappingHandlerAdapter" für den Aufruf der Handler-Methode verantwortlich.
Diese Klasse löst den an das Argument der Handler-Methode übergebenen Wert auf und behandelt den Rückgabewert. Es ist hilfreich, "Model" oder "RedirectAttributes" als Argument der Handler-Methode anzugeben. Arbeitet hart.
Es ist also die Rolle der Implementierungsklasse von "HandlerMethodArgumentResolver", den an das Argument übergebenen Wert tatsächlich aufzulösen. Wenn "@ ModelAttribute" angegeben wird, wird "ModelAttributeMethodProcessor" verarbeitet.
Dies war nicht das Hauptthema, aber ich werde auch die Funktionsweise der Methode mit @ ModelAttribute
zusammenfassen.
Der Vorgang des Speicherns des Rückgabewerts der Methode in Model
wird ausgeführt, bevor die Handler-Methode ausgeführt wird.
(Genau genommen ist es "ModelMap" in "ModelAndViewContainer", aber es scheint verwirrend zu sein, also werde ich es "Model" nennen.)
Angenommen, Sie erstellen den folgenden Controller.
@Controller
public class HelloController {}
@ModelAttribute
public User setUp() {
return new User();
}
@GetMapping("/")
public String index() {
return "hello";
}
}
Das Verhalten zu diesem Zeitpunkt ist wie folgt. (Genau genommen ist es nur ein Bild.)
@Controller
public class HelloController {
@GetMapping("/")
public String index(Model model) {
model.addAttribute(new User());
return "hello";
}
}
Wenn der Attributname wie unten gezeigt in @ ModelAttriubte
angegeben ist,
@ModelAttribute("hoge")
public User setUp() {
return new User();
}
Das Bild, das dem ersten Argument von "model.addAttribute ()" hinzugefügt wurde.
@GetMapping("/")
public String index(Model model) {
model.addAttribute("hoge", new User());
return "index";
}
Übrigens ist es die Klasse namens "ModelFactory", die die Methode mit "@ ModelAttribute" aufruft.
Das Hauptthema ist von hier.
Holen Sie sich zuerst das angegebene Objekt von Model
. Wenn für "@ ModelAttribute" "value" oder "name" angegeben ist, kann der Schlüssel zum Abrufen von "Model" angegeben werden. Wenn nicht angegeben, wird Spring automatisch aus dem Klassennamen ermittelt.
Es gibt verschiedene Muster, die von "Model" abgerufen werden können, z. B. das Speichern in "Model" mit der oben erwähnten Methode mit "@ ModelAttribute", das Verwenden des Flash-Bereichs zum Zeitpunkt der Umleitung und das Festlegen des Sitzungsbereichs mit "@ SessionAttribute". Wenn Sie es verwenden, wird es möglicherweise in Model
gespeichert, bevor die Handler-Methode ausgeführt wird (oder besser gesagt, bevor der an das Argument übergebene Wert aufgelöst wird).
Wenn das Objekt nicht aus "Modell" abgerufen werden kann, wird ein Objekt erstellt. Wenn es einen Konstruktor gibt, wird dieser verwendet. Wenn mehrere Konstruktoren vorhanden sind, wird der Standardkonstruktor verwendet. Wenn jedoch zu diesem Zeitpunkt kein Standardkonstruktor vorhanden ist, stirbt er ab. Bei Verwendung eines Konstruktors mit Argumenten wird der Prozess des Bindens von Anforderungsparametern eingefügt, der später beschrieben wird.
Binden Sie den Wert des Anforderungsparameters an das erfasste oder generierte Objekt. Ein Wert ist an das Feld des Objekts gebunden, das dem Namen des Anforderungsparameters entspricht.
Überschreibt den Wert des Objekts, tut jedoch nichts für Werte, die nicht in den Anforderungsparametern enthalten sind.
@Controller
public class HelloController {
@ModelAttribute
public User setUp() {
User user = new User();
user.setName("Name");
user.setEmail("Email");
return user;
}
@GetMapping("/")
public String index(User user) {
return "hello";
}
}
Angenommen, "Benutzer" hat Felder "Name" und "E-Mail". Dann wird, wie unten gezeigt, nur "Name" zum Anforderungsparameter hinzugefügt und gesendet.
curl http://localhost:8080?name=hogehoge
Der "Name" des "Benutzer" -Objekts wird zu "hogehoge", "email" wird zu "mail" und wird nicht durch "null" überschrieben.
Versuchen Sie beim Erstellen eines Objekts mit einem Konstruktor mit Parametern, die Daten zu diesem Zeitpunkt zu binden. Binden Sie in diesem Fall den Anforderungsparameter, der dem im Konstruktorparameternamen oder "@ ConstructorProperties" angegebenen Wert entspricht, anstelle des Feldnamens.
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;
}
}
Im Fall des obigen Konstruktors muss der Name des Anforderungsparameters "n" oder "e" sein.
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;
}
}
Wenn Sie wie oben "@ ConstructorProperties" verwenden, kann der Name des Anforderungsparameters "Name" oder "E-Mail" sein. Wenn es "@ ConstructorProperties" gibt, wird es priorisiert, so dass es nicht mit "n" oder "e" funktioniert.
Nach Abschluss der Objekterstellung wird jedoch derselbe Vorgang zum Binden des Anforderungsparameters wie üblich ausgeführt. Wenn also ein Setter vorhanden ist oder wenn Sie "DataBinder" verwenden, der den direkten Feldzugriff ermöglicht, ist dies der Fall Das Ergebnis von hat Priorität.
Zum Beispiel, wenn Sie die folgende Klasse erstellen
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;
}
}
Im Konstruktor werden "hoge" und "fuga" hinzugefügt und im Feld gehalten, aber da der Setter danach ausgeführt wird, werden "hoge" und "fuga" zu dem Wert hinzugefügt, der endgültig gebunden werden soll. Es war nicht so.
Durch Ändern von "Bindung" von "@ ModelAttribute" in "Falsch" ist es möglich, die Bindung von Anforderungsparametern zu unterdrücken.
@Controller
public class HelloController {
@ModelAttribute
public User setUp() {
User user = new User();
user.setName("Name");
user.setEmail("Email");
return user;
}
@GetMapping("/")
public String index(@ModelAttribute(binding = false) User user) {
return "hello";
}
}
Selbst wenn die folgende Anfrage im obigen Zustand gesendet wird, bleibt der "Name" des Objekts "Benutzer" "Name" und "E-Mail" bleibt "Mail".
curl http://localhost:8080?name=hogehoge&email=fugafuga
Führen Sie eine Validierung durch, wenn eine Validierung erforderlich ist, z. B. wenn dem Argument "@ Validated" hinzugefügt wird.
Wenn aufgrund der Validierung ein Fehler auftritt und das Argument unmittelbar nach der Handler-Methode keine "Fehler" enthält, wird "BindingException" ausgelöst. Wenn dies der Fall ist, wird das Ergebnis als "BindingResult" beibehalten. (Errors
ist die übergeordnete Schnittstelle von BindingResult
)
Übrigens, wenn Sie "BindingResult" als Argument in der Handler-Methode festlegen, müssen Sie wegen der Implementierung hier vorsichtig sein.
Die Geschichte geht schief, aber wenn Sie das Argument der Handler-Methode auf "BindingResult" setzen, löst die Klasse "ErrorsMethodArgumentResolver" den Wert des Arguments auf.
Diese Klasse prüft, ob das letzte in Model
gespeicherte Element BindingResult
ist, und setzt es in diesem Fall im Argument.
Daher funktioniert es möglicherweise nicht ordnungsgemäß, es sei denn, es wird unmittelbar nach der Validierung und dem Speichern von "BindingResult" in "Model" ausgeführt.
Daher scheint es, dass das Argument der Handler-Methode unmittelbar nach dem Argument mit "@ Validated" "BindingResult" haben muss.
Nachdem ich dies untersucht hatte, dachte ich, dass es möglich ist, Code zu realisieren, der das Objekt, das den Anforderungsparameter bindet, in zwei Teile teilt und das Validierungsergebnis von jedem wie folgt erhält. Das Validierungsergebnis von "Benutzer" wird in "Ergebnis1" gespeichert, und das Validierungsergebnis von "OtherObj" wird in "Ergebnis2" gespeichert.
@Controller
public class HelloController {
@GetMapping("/")
public String index(@Validated User user, BindingResult result1, @Validated OtherObj other, BindingResult result2) {
return "index";
}
}
Wenn Sie einen Konstruktor mit Parametern verwenden, wird die Typkonvertierung zum Zeitpunkt der Erstellung des Objekts nach Bedarf durchgeführt. Wenn zu diesem Zeitpunkt ein Feld enthalten ist, das nicht typkonvertiert werden kann, wird das Ergebnis als "BindingResult" beibehalten, die anschließende Verarbeitung und Validierung der Bindung wird jedoch nicht durchgeführt. Mit anderen Worten, die Verarbeitung der Anforderungsparameterbindung durch den Setter und die Feldprüfung durch die Bean-Validierung werden nicht durchgeführt.
public class User {
@NotEmpty
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
//Getter weggelassen
}
@Controller
public class HelloController {
@GetMapping("/")
public String index(@Validated User user, BindingResult result) {
return "index";
}
}
Wenn Sie eine Klasse wie die oben beschriebene erstellen, senden Sie die folgende Anfrage.
curl http://localhost:8080?age=hogehoge
Das erwartete Validierungsergebnis ist, dass "Name" leer ist und "Alter" nicht in "Ganzzahl" konvertiert werden kann, sondern nur letztere erkannt werden kann. Beides kann erkannt werden, indem der parametrisierte Konstruktor entfernt und geändert wird, um den Standardkonstruktor und den Setter zu verwenden.
Speichern Sie das durch den obigen Prozess erstellte Objekt und BindingResult
in Model
.
Wenn der Attributname in "@ ModelAttribute" angegeben ist, verwenden Sie diesen Wert als Schlüssel.
Aha. Wenn Sie einen Konstruktor mit Parametern verwenden, müssen Sie vorsichtig sein, da die Operation geringfügig abweichen kann.
Recommended Posts