web-dev-qa-db-fra.com

Spring MVC - Pourquoi ne pas utiliser @RequestBody et @RequestParam ensemble

Utilisation du client de développement HTTP avec une demande de publication et une application de type de contenu/x-www-form-urlencoded

1) Seulement @RequestBody

Demande - localhost: 8080/SpringMVC/welcome In Body - nom = abc

Code-

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

// donne le corps comme 'nom = abc' comme prévu

2) Seulement @RequestParam

Demande - localhost: 8080/SpringMVC/welcome In Body - nom = abc

Code-

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

// Donne le nom comme 'abc' comme prévu

3) les deux ensemble

Demande - localhost: 8080/SpringMVC/welcome In Body - nom = 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";
}

// Code d'erreur HTTP 400 - La demande envoyée par le client était syntaxiquement incorrecte.

4) Ci-dessus avec params changé de position

Demande - localhost: 8080/SpringMVC/welcome In Body - nom = 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";
}

// Pas d'erreur. Le nom est 'abc'. le corps est vide

5) Ensemble mais obtenez les paramètres de type url

Demande - localhost: 8080/SpringMVC/welcome? Name = xyz Dans 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";
}

// nom est 'xyz' et le corps est 'name = abc'

6) Identique à 5) mais avec les paramètres de position modifiés

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

// nom = 'xyz, abc' le corps est vide

Quelqu'un peut-il expliquer ce comportement?

56
abhihello123

Le @RequestBody états javadoc

L'annotation indiquant un paramètre de méthode doit être liée au corps de la demande Web.

Il utilise des instances enregistrées de HttpMessageConverter pour désérialiser le corps de la demande en un objet du type de paramètre annoté.

Et @RequestParam

Annotation indiquant qu'un paramètre de méthode doit être lié à un paramètre de requête Web.

  1. Spring lie le corps de la requête au paramètre annoté avec @RequestBody.

  2. Spring lie les paramètres de requête du corps de la requête (paramètres encodés en URL) au paramètre de votre méthode. Spring utilisera le nom du paramètre, c'est-à-dire. name, pour mapper le paramètre.

  3. Les paramètres sont résolus dans l'ordre. Le @RequestBody est traité en premier. Spring utilisera tous les HttpServletRequestInputStream. Quand il essaie ensuite de résoudre le @RequestParam, qui est par défaut required, il n'y a pas de paramètre de requête dans la chaîne de requête ou ce qui reste du corps de la requête, c'est-à-dire. rien. Donc, il échoue avec 400 parce que la demande ne peut pas être correctement traitée par la méthode du gestionnaire.

  4. Le gestionnaire pour @RequestParam _ _ agit d'abord en lisant ce qu'il peut du HttpServletRequestInputStream pour mapper le paramètre de requête, c'est-à-dire. l'ensemble de la requête/les paramètres encodés dans l'url. Il le fait et obtient la valeur abc mappée sur le paramètre name. Quand le gestionnaire pour @RequestBody s'exécute, il ne reste plus rien dans le corps de la demande, l'argument utilisé est donc la chaîne vide.

  5. Le gestionnaire pour @RequestBody lit le corps et le lie au paramètre. Le gestionnaire pour @RequestParam peut ensuite obtenir le paramètre de requête à partir de la chaîne de requête URL.

  6. Le gestionnaire pour @RequestParam lit à la fois le corps et la chaîne de requête URL. Cela les mettrait généralement dans un Map, mais comme le paramètre est de type String, Spring sérialisera le Map sous forme de valeurs séparées par des virgules. Le gestionnaire pour @RequestBody alors, encore une fois, n’a plus rien à lire dans le corps.

47

Il est trop tard pour répondre à cette question, mais cela pourrait aider les nouveaux lecteurs. J'ai exécuté tous ces tests avec le printemps 4.1.4 et constaté que l'ordre de @RequestBody et @RequestParam _ n'a pas d'importance.

  1. identique à votre résultat
  2. identique à votre résultat
  3. a donné body= "name=abc", et name = "abc"
  4. Identique à 3.
  5. body ="name=abc", name = "xyz,abc"
  6. identique à 5.
3
Ramesh Karna

Cela se produit à cause d'une spécification Servlet pas très simple. Si vous travaillez avec une implémentation native HttpServletRequest, vous ne pouvez pas obtenir à la fois le corps du code de l'URL et les paramètres. Spring propose des solutions de contournement qui le rendent encore plus étrange et non transparent.

Dans ce cas, Spring (version 3.2.4) restitue un corps pour vous en utilisant les données de la méthode getParameterMap(). Il mélange les paramètres GET et POST et rompt l'ordre des paramètres. La classe responsable du chaos est ServletServerHttpRequest. Malheureusement, elle ne peut pas être remplacée, mais la classe StringHttpMessageConverter peut être.

La solution propre n'est malheureusement pas simple:

  1. Remplacement de StringHttpMessageConverter. Copier/écraser la méthode de réglage de classe d'origine readInternal().
  2. Envelopper les méthodes HttpServletRequest en remplaçant les méthodes getInputStream(), getReader() et getParameter*().

Dans la méthode, StringHttpMessageConverter # readInternal doit être utilisé:

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

Ensuite, le convertisseur doit être enregistré dans le contexte.

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

La deuxième étape est décrite ici: La demande de servlet HTTP perd les paramètres de POST le corps après l'avoir lu une fois

1
30thh

Vous pouvez également modifier l'état requis par défaut de @RequestParam sur false afin que le code d'état de réponse HTTP 400 ne soit pas généré. Cela vous permettra de placer les annotations dans l'ordre de votre choix.

@RequestParam(required = false)String name
0
Sparticles