wake-up-neo.net

Spring MVC - Warum können @RequestBody und @RequestParam nicht zusammen verwendet werden?

Verwenden des HTTP-Dev-Clients mit Post-Request und Content-Type-Anwendung/x-www-form-urlencoded

1) Nur @RequestBody

Anfrage - localhost: 8080/SpringMVC/welcome In Körpername = abc

Code-

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, Model model) {
    model.addAttribute("message", body);
    return "hello";
}

// Gibt den Körper wie erwartet als 'name = abc' aus

2) Nur @RequestParam

Anfrage - localhost: 8080/SpringMVC/welcome In Körpername = abc

Code-

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, Model model) {
    model.addAttribute("name", name);
    return "hello";
}

// Gibt den Namen wie erwartet als 'abc' aus

3) Beides zusammen

Anfrage - localhost: 8080/SpringMVC/welcome In Körpername = abc

Code-

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, @RequestParam String name, Model model) {
    model.addAttribute("name", name);
    model.addAttribute("message", body);
    return "hello";
}

// HTTP-Fehlercode 400 - Die vom Client gesendete Anforderung war syntaktisch falsch.

4) Oben mit geänderter Position der Parameter

Anfrage - localhost: 8080/SpringMVC/welcome In Körpername = abc

Code-

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, @RequestBody String body, Model model) {
    model.addAttribute("name", name);
    model.addAttribute("message", body);
    return "hello";
}

// Kein Fehler. Der Name ist 'abc'. Körper ist leer

5) Geben Sie zusammen aber URL-Parameter ein

Anfrage - localhost: 8080/SpringMVC/welcome? Name = xyz In Body - name = abc

Code-

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestBody String body, @RequestParam String name, Model model) {
    model.addAttribute("name", name);
    model.addAttribute("message", body);
    return "hello";
}

// name ist 'xyz' und body ist 'name = abc'

6) Wie 5), jedoch mit geänderter Parameterposition

Code -

@RequestMapping(method = RequestMethod.POST)
public String printWelcome(@RequestParam String name, @RequestBody String body, Model model) {
    model.addAttribute("name", name);
    model.addAttribute("message", body);
    return "hello";
}

// name = 'xyz, abc' body ist leer

Kann jemand dieses Verhalten erklären?

56
abhihello123

Der @RequestBody - Javadoc gibt an

Anmerkungen, die einen Methodenparameter angeben, sollten an den Hauptteil der Webanforderung gebunden sein.

Es verwendet registrierte Instanzen von HttpMessageConverter, um den Anforderungshauptteil in ein Objekt des mit Anmerkungen versehenen Parametertyps zu deserialisieren.

Und @RequestParam

Anmerkung, die angibt, dass ein Methodenparameter an einen Webanforderungsparameter gebunden werden soll.

  1. Spring bindet den Body der Anfrage an den mit @RequestBody Gekennzeichneten Parameter.

  2. Spring bindet Anforderungsparameter aus dem Anforderungshauptteil (url-codierte Parameter) an Ihren Methodenparameter. Spring verwendet den Namen des Parameters, d. H. name, um den Parameter abzubilden.

  3. Parameter werden der Reihe nach aufgelöst. Der @RequestBody Wird zuerst verarbeitet. Der Frühling verbraucht alle HttpServletRequestInputStream. Wenn es dann versucht, den @RequestParam Aufzulösen, der standardmäßig required ist, gibt es keinen Anforderungsparameter in der Abfragezeichenfolge oder was vom Anforderungshauptteil übrig bleibt, d. H. nichts. Daher schlägt es mit 400 fehl, da die Anforderung von der Handlermethode nicht korrekt verarbeitet werden kann.

  4. Der Handler für @RequestParam Handelt zuerst und liest, was er von HttpServletRequestInputStream kann, um den Anforderungsparameter abzubilden, dh. die gesamte Abfragezeichenfolge/URL-codierte Parameter. Dabei wird der Wert abc dem Parameter name zugeordnet. Wenn der Handler für @RequestBody Ausgeführt wird, ist im Anforderungshauptteil nichts mehr vorhanden. Daher wird als Argument die leere Zeichenfolge verwendet.

  5. Der Handler für @RequestBody Liest den Body und bindet ihn an den Parameter. Der Handler für @RequestParam Kann dann den Anforderungsparameter aus der URL-Abfragezeichenfolge abrufen.

  6. Der Handler für @RequestParam Liest sowohl aus dem Text als auch aus der URL-Abfragezeichenfolge. Normalerweise werden sie in ein Map eingefügt. Da der Parameter jedoch vom Typ String ist, serialisiert Spring das Map als durch Kommas getrennte Werte. Der Handler für @RequestBody Hat dann wiederum nichts mehr, was er aus dem Body lesen könnte.

47

Es ist zu spät, um diese Frage zu beantworten, aber es könnte für neue Leser helfen. Es scheint, dass es Versionsprobleme gibt. Ich habe all diese Tests mit Spring 4.1.4 durchgeführt und festgestellt, dass die Reihenfolge von @RequestBody und @RequestParam spielt keine Rolle.

  1. das gleiche wie Ihr Ergebnis
  2. das gleiche wie Ihr Ergebnis
  3. gegeben body= "name=abc", und name = "abc"
  4. Gleich wie 3.
  5. body ="name=abc", name = "xyz,abc"
  6. wie 5.
3
Ramesh Karna

Dies liegt an der nicht ganz einfachen Servlet-Spezifikation. Wenn Sie mit einer systemeigenen HttpServletRequest -Implementierung arbeiten, können Sie nicht sowohl den URL-Codierungshauptteil als auch die Parameter abrufen. Spring macht einige Workarounds, die es noch seltsamer und undurchsichtiger machen.

In solchen Fällen rendert Spring (Version 3.2.4) einen Body unter Verwendung von Daten aus der getParameterMap() -Methode neu. Es mischt die Parameter GET und POST und unterbricht die Parameterreihenfolge. Die Klasse, die für das Chaos verantwortlich ist, ist ServletServerHttpRequest. Leider kann sie nicht ersetzt werden, aber die Klasse StringHttpMessageConverter kann sein.

Die saubere Lösung ist leider nicht einfach:

  1. StringHttpMessageConverter ersetzen. Kopieren/Überschreiben Sie die ursprüngliche Klassenanpassungsmethode readInternal().
  2. Umbrechen von HttpServletRequest Überschreibungsmethoden getInputStream(), getReader() und getParameter*().

In der Methode StringHttpMessageConverter # readInternal muss folgender Code verwendet werden:

    if (inputMessage instanceof ServletServerHttpRequest) {
        ServletServerHttpRequest oo = (ServletServerHttpRequest)inputMessage;
        input = oo.getServletRequest().getInputStream();
    } else {
        input = inputMessage.getBody();
    }

Dann muss der Konverter im Kontext registriert werden.

<mvc:annotation-driven>
    <mvc:message-converters register-defaults="true/false">
        <bean class="my-new-converter-class"/>
   </mvc:message-converters>
</mvc:annotation-driven>

Der zweite Schritt wird hier beschrieben: HTTP-Servlet-Anforderung verliert Parameter von POST body nach einmaligem Lesen

1
30thh

Sie können auch einfach den voreingestellten Status von @RequestParam in false ändern, damit der HTTP-Antwortstatuscode 400 nicht generiert wird. Auf diese Weise können Sie die Anmerkungen in beliebiger Reihenfolge platzieren.

@RequestParam(required = false)String name
0
Sparticles