[JAVA] Ich möchte den Ablauf der Spring-Verarbeitungsanforderungsparameter verstehen

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.

Umgebung

Wobei @ 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.

Verhalten, wenn der Methode "@ ModelAttribute" zugewiesen wird

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.

Verhalten, wenn dem Argument der Handler-Methode "@ ModelAttribute" hinzugefügt wird

Das Hauptthema ist von hier.

Objekte abrufen und erstellen

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.

Parameterbindung anfordern

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.

Beim Erstellen eines Objekts mit einem Konstruktor mit Parametern

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.

Anforderungsparameterbindung unterdrücken

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

Validierung

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";
    }
}

Bei Verwendung eines Konstruktors mit Parametern

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.

Im Modell speichern

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.

schließlich

Aha. Wenn Sie einen Konstruktor mit Parametern verwenden, müssen Sie vorsichtig sein, da die Operation geringfügig abweichen kann.

Recommended Posts

Ich möchte den Ablauf der Spring-Verarbeitungsanforderungsparameter verstehen
Ich möchte die Standardfehlermeldung von Spring Boot steuern
Ich möchte den Inhalt der Absicht var_dump
[Spring Boot] Ich habe untersucht, wie die Nachbearbeitung der empfangenen Anforderung implementiert werden kann.
05. Ich habe versucht, die Quelle von Spring Boot zu löschen
Ich habe versucht, die Kapazität von Spring Boot zu reduzieren
Ich möchte die Antwort der Janken-App wissen
Ich möchte den Namen des Posters des Kommentars anzeigen
Steuern Sie den Spring Batch-Verarbeitungsablauf mit JavaConfig.
Ich möchte den Inhalt der Anfrage sehen, ohne vier oder fünf zu sagen
Ich möchte die Bildlaufposition von UITableView zurückgeben!
Ich möchte die Protokollausgabeeinstellung von UtilLoggingJdbcLogger ändern
[Ruby] Ich möchte die Reihenfolge der Hash-Tabelle umkehren
Die Geschichte von Collectors.groupingBy, die ich für die Nachwelt behalten möchte
Ich möchte die Eingabe begrenzen, indem ich den Zahlenbereich einschränke
Ich möchte den Wert von Attribute in Selenium of Ruby ändern
Ich habe die interne Verarbeitung von Retrofit untersucht
Ich möchte die Methode des Controllers kennen, bei der die Ausnahme im ExceptionHandler von Spring Boot ausgelöst wurde
Ich möchte Bilder mit REST Controller von Java und Spring anzeigen!
Ich möchte die JSP des offenen Portlets bei der Entwicklung von Liferay kennen
[Active Admin] Ich möchte die Standardverarbeitung zum Erstellen und Aktualisieren anpassen
[Ruby] Ich möchte nur den Wert des Hash und nur den Schlüssel extrahieren
Bestätigung und Umgestaltung des Flusses von der Anfrage zum Controller in [httpclient]
Ich habe den Ablauf der TCP-Kommunikation mit Spring Integration (Client Edition) untersucht.
Ich habe den Ablauf der TCP-Kommunikation mit Spring Integration (Server Edition) untersucht.
Ich möchte das Argument der Annotation und das Argument der aufrufenden Methode an den Aspekt übergeben
Ich möchte den Feldnamen des [Java] -Felds erhalten. (Alter Ton)
[Rails] Artikel für Anfänger, um den Fluss von form_with zu organisieren und zu verstehen
Ich möchte, dass Sie Enum # name () für den Schlüssel von SharedPreference verwenden
Die Geschichte der Erhöhung der Spring Boot 1.5-Serie auf die 2.1-Serie
Ich möchte nach dem Dezimalpunkt abschneiden
Ich möchte die Aggregationsverarbeitung mit Spring-Batch durchführen
Ich möchte den Wert in Ruby erhalten
Teil 2: Verstehen Sie (ungefähr) den Prozessablauf der OAuth 2.0-Anmeldung, die von Spring Security 5 unterstützt wird
Ich möchte den Wert von Cell unabhängig vom CellType (Apache POI) transparent erhalten.
03. Ich habe eine Anfrage von Spring Boot an die Postleitzahlensuch-API gesendet
Teil 3: Verstehen Sie den Prozessablauf der OAuth 2.0-Anmeldung, die von Spring Security 5 unterstützt wird, (gründlich)
Ich möchte rekursiv die Oberklasse und die Schnittstelle einer bestimmten Klasse erhalten
Ich möchte eine Methode einer anderen Klasse aufrufen
[JavaScript] Ich möchte die Verarbeitung einschränken, indem ich Eingabeelemente überprüfe, wenn die angegebene Zeit zum Zeitpunkt des Fokusausgangs abgelaufen ist.
[Java] Ich möchte die Differenz zum Datum berechnen
Ich möchte eine TraceId in das Protokoll einbetten
Ich habe versucht, den Betrieb der http-Anfrage (Put) mit dem Talented API Tester zu überprüfen
Verstehe beim Lesen des Artikels! Zusammenfassung der Inhalte, die Elasticsearch-Anfänger unterdrücken möchten
Verstehen Sie die Eigenschaften von Scala in 5 Minuten (Einführung in Scala)
Ich möchte den Bereich anhand des monatlichen Abschlusses beurteilen
Ich möchte den Dunkelmodus mit der SWT-App verwenden