Passer plusieurs variables dans @RequestBody à un contrôleur Spring MVC à l'aide d'Ajax

113

Est-il nécessaire d'envelopper un objet de support? Je veux faire ça:

@RequestMapping(value = "/Test", method = RequestMethod.POST)
@ResponseBody
public boolean getTest(@RequestBody String str1, @RequestBody String str2) {}

Et utilisez un JSON comme celui-ci:

{
    "str1": "test one",
    "str2": "two test"
}

Mais à la place, je dois utiliser:

@RequestMapping(value = "/Test", method = RequestMethod.POST)
@ResponseBody
public boolean getTest(@RequestBody Holder holder) {}

Et puis utilisez ce JSON:

{
    "holder": {
        "str1": "test one",
        "str2": "two test"
    }
}

Est-ce exact? Mon autre option serait de changer le RequestMethoden GETet de l'utiliser @RequestParamdans la chaîne de requête ou de l'utiliser @PathVariableavec l'un ou l' autre RequestMethod.

NimChimpsky
la source

Réponses:

92

Vous avez raison, le paramètre annoté @RequestBody devrait contenir tout le corps de la requête et se lier à un objet, vous devrez donc essentiellement choisir vos options.

Si vous voulez absolument votre approche, il existe une implémentation personnalisée que vous pouvez faire:

Dites que c'est votre json:

{
    "str1": "test one",
    "str2": "two test"
}

et vous voulez le lier aux deux paramètres ici:

@RequestMapping(value = "/Test", method = RequestMethod.POST)
public boolean getTest(String str1, String str2)

Définissez d'abord une annotation personnalisée, par exemple @JsonArg, avec le chemin JSON comme le chemin vers les informations souhaitées:

public boolean getTest(@JsonArg("/str1") String str1, @JsonArg("/str2") String str2)

Maintenant, écrivez un HandlerMethodArgumentResolver personnalisé qui utilise le JsonPath défini ci-dessus pour résoudre l'argument réel:

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import com.jayway.jsonpath.JsonPath;

public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{

    private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(JsonArg.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String body = getRequestBody(webRequest);
        String val = JsonPath.read(body, parameter.getMethodAnnotation(JsonArg.class).value());
        return val;
    }

    private String getRequestBody(NativeWebRequest webRequest){
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        String jsonBody = (String) servletRequest.getAttribute(JSONBODYATTRIBUTE);
        if (jsonBody==null){
            try {
                String body = IOUtils.toString(servletRequest.getInputStream());
                servletRequest.setAttribute(JSONBODYATTRIBUTE, body);
                return body;
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return "";

    }
}

Maintenant, enregistrez-le simplement avec Spring MVC. Un peu impliqué, mais cela devrait fonctionner proprement.

Biju Kunjummen
la source
2
Comment créer une annotation personnalisée, dites @JsonArg s'il vous plaît?
Surendra Jnawali
Pourquoi est-ce? maintenant, je dois créer beaucoup de classes wrapper différentes dans le backend. Je migre une application Struts2 vers Springboot et il y a eu de nombreux cas où les objets JSON envoyés à l'aide d'ajax sont en fait deux ou plusieurs objets du modèle: par exemple, un utilisateur et une activité
Jose Ospina
ce lien vous montre "comment enregistrer ceci avec Spring MVC" geekabyte.blogspot.sg/2014/08
Bodil
3
toujours intéressant pourquoi cette option n'est pas ajoutée au printemps. cela semble être une option logique lorsque vous avez comme 2 longs et que vous n'avez pas l'habitude de créer un objet wrapper pour cela
tibi
@SurendraJnawali vous pouvez le faire comme ça@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JsonArg { String value() default ""; }
Epono
88

S'il est vrai qu'il @RequestBodydoit correspondre à un seul objet, cet objet peut être un Map, donc cela vous donne un bon moyen d'atteindre ce que vous essayez d'atteindre (pas besoin d'écrire un objet de sauvegarde unique):

@RequestMapping(value = "/Test", method = RequestMethod.POST)
@ResponseBody
public boolean getTest(@RequestBody Map<String, String> json) {
   //json.get("str1") == "test one"
}

Vous pouvez également vous lier à ObjectNode de Jackson si vous voulez une arborescence JSON complète:

public boolean getTest(@RequestBody ObjectNode json) {
   //json.get("str1").asText() == "test one"
Kong
la source
@JoseOspina pourquoi ne peut pas le faire. Tout risque associé à Map <String, Object> avec requestBody
Ben Cheng
@Ben Je veux dire que vous pouvez utiliser UN seul Mapobjet pour stocker n'importe quel nombre d'objets à l'intérieur, mais l'objet de niveau supérieur doit toujours être un seul, il ne peut pas y avoir deux objets de niveau supérieur.
Jose Ospina du
1
Je pense que l'inconvénient d'une approche dynamique comme Map<String, String>c'est: les bibliothèques de documentation API (swagger / springfox, etc.) ne seront probablement pas en mesure d'analyser votre schéma de demande / réponse à partir de votre code source.
stratovarius
10

Vous pouvez mélanger l'argument post en utilisant la variable body et path pour des types de données plus simples:

@RequestMapping(value = "new-trade/portfolio/{portfolioId}", method = RequestMethod.POST)
    public ResponseEntity<List<String>> newTrade(@RequestBody Trade trade, @PathVariable long portfolioId) {
...
}
shrikeac
la source
10

Pour passer plusieurs objets, paramètres, variables, etc. Vous pouvez le faire de manière dynamique en utilisant ObjectNode de la bibliothèque jackson comme paramètre. Vous pouvez le faire comme ceci:

@RequestMapping(value = "/Test", method = RequestMethod.POST)
@ResponseBody
public boolean getTest(@RequestBody ObjectNode objectNode) {
   // And then you can call parameters from objectNode
   String strOne = objectNode.get("str1").asText();
   String strTwo = objectNode.get("str2").asText();

   // When you using ObjectNode, you can pas other data such as:
   // instance object, array list, nested object, etc.
}

J'espère que cette aide.

azwar_akbar
la source
2

@RequestParamest le paramètre HTTP GETou POSTenvoyé par le client, le mappage de demande est un segment d'URL dont la variable:

http:/host/form_edit?param1=val1&param2=val2

var1& var2sont des paramètres de requête.

http:/host/form/{params}

{params}est un mappage de demande. vous pouvez appeler votre service comme: http:/host/form/userou http:/host/form/firm où l'entreprise et l'utilisateur sont utilisés comme Pathvariable.

psisodia
la source
cela ne répond pas à la question et est faux, vous n'utilisez pas de chaîne de requête avec les requêtes POST
NimChimpsky
1
@NimChimpsky: bien sûr que vous le pouvez. Une requête POST peut toujours inclure des paramètres dans l'URL.
Martijn Pieters
2

La solution simple consiste à créer une classe de charge utile qui a les attributs str1 et str2:

@Getter
@Setter
public class ObjHolder{

String str1;
String str2;

}

Et après vous pouvez passer

@RequestMapping(value = "/Test", method = RequestMethod.POST)
@ResponseBody
public boolean getTest(@RequestBody ObjHolder Str) {}

et le corps de votre demande est:

{
    "str1": "test one",
    "str2": "two test"
}
Nbenz
la source
1
Quel est le paquet de ces annotations? Autoimport ne proposait que l'importation jdk.nashorn.internal.objects.annotations.Setter; ÉDITER. Je suppose que c'est Lombok projectlombok.org/features/GetterSetter . Veuillez me corriger si je me trompe
Gleichmut
@Gleichmut vous pouvez utiliser de simples getters et setter pour vos variables. Cela fonctionnera comme prévu.
Gimnath le
1

Au lieu d'utiliser json, vous pouvez faire des choses simples.

$.post("${pageContext.servletContext.contextPath}/Test",
                {
                "str1": "test one",
                "str2": "two test",

                        <other form data>
                },
                function(j)
                {
                        <j is the string you will return from the controller function.>
                });

Maintenant, dans le contrôleur, vous devez mapper la demande ajax comme ci-dessous:

 @RequestMapping(value="/Test", method=RequestMethod.POST)
    @ResponseBody
    public String calculateTestData(@RequestParam("str1") String str1, @RequestParam("str2") String str2, HttpServletRequest request, HttpServletResponse response){
            <perform the task here and return the String result.>

            return "xyz";
}

J'espère que cela vous aide.

Japon Trivedi
la source
1
C'est json, et ça ne marche pas. Vous spécifiez requestparam dans la méthode, mais définissez equestbody avec json dans la demande de publication ajax.
NimChimpsky
Voir Je n'ai pas utilisé le format JSON dans l'appel ajax. J'ai simplement utilisé deux paramètres de requête et dans le contrôleur, nous pouvons obtenir ces paramètres avec l'annotation @RequestParam. Ça fonctionne. J'utilise ça. Essayez-le.
Japan Trivedi
J'ai essayé cela, c'est le point de la question. Ça ne marche pas comme ça.
NimChimpsky
Veuillez préciser exactement ce que vous avez essayé. Montrez-le dans votre question. Je pense que vous avez une exigence différente de ce que j'ai compris.
Japan Trivedi
1
A travaillé pour moi du premier coup. Merci!
Humppakäräjät le
1

J'ai adapté la solution de Biju:

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.io.IOUtils;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;


public class JsonPathArgumentResolver implements HandlerMethodArgumentResolver{

    private static final String JSONBODYATTRIBUTE = "JSON_REQUEST_BODY";

    private ObjectMapper om = new ObjectMapper();

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(JsonArg.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String jsonBody = getRequestBody(webRequest);

        JsonNode rootNode = om.readTree(jsonBody);
        JsonNode node = rootNode.path(parameter.getParameterName());    

        return om.readValue(node.toString(), parameter.getParameterType());
    }


    private String getRequestBody(NativeWebRequest webRequest){
        HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);

        String jsonBody = (String) webRequest.getAttribute(JSONBODYATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);
        if (jsonBody==null){
            try {
                jsonBody = IOUtils.toString(servletRequest.getInputStream());
                webRequest.setAttribute(JSONBODYATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return jsonBody;

    }

}

Quelle est la différence:

  • J'utilise Jackson pour convertir json
  • Je n'ai pas besoin d'une valeur dans l'annotation, vous pouvez lire le nom du paramètre dans MethodParameter
  • J'ai également lu le type du paramètre dans Methodparameter => afin que la solution soit générique (je l'ai testé avec une chaîne et des DTO)

BR

user3227576
la source
0

Le paramètre de requête existe pour GET et POST, pour Get, il sera ajouté en tant que chaîne de requête à l'URL mais pour POST, il se trouve dans le corps de la requête

Kaleem
la source
0

Je ne sais pas où vous ajoutez le json, mais si je le fais comme ça avec angular, cela fonctionne sans la requêteBody: angluar:

    const params: HttpParams = new HttpParams().set('str1','val1').set('str2', ;val2;);
    return this.http.post<any>( this.urlMatch,  params , { observe: 'response' } );

Java:

@PostMapping(URL_MATCH)
public ResponseEntity<Void> match(Long str1, Long str2) {
  log.debug("found: {} and {}", str1, str2);
}
tibi
la source
0

Bien. Je suggère de créer un objet de valeur (Vo) qui contient les champs dont vous avez besoin. Le code est plus simple, on ne change pas le fonctionnement de Jackson et c'est encore plus facile à comprendre. Cordialement!

Matias Zamorano
la source
0

Vous pouvez réaliser ce que vous voulez en utilisant @RequestParam. Pour cela, vous devez procéder comme suit:

  1. Déclarez les paramètres RequestParams qui représentent vos objets et définissez l' requiredoption sur false si vous souhaitez pouvoir envoyer une valeur nulle.
  2. Sur le frontend, stringify les objets que vous souhaitez envoyer et les inclure en tant que paramètres de requête.
  3. Sur le backend, remettez les chaînes JSON dans les objets qu'elles représentent en utilisant Jackson ObjectMapper ou quelque chose comme ça, et le tour est joué!

Je sais, c'est un peu un hack mais ça marche! ;)

Maurice
la source
0

vous pouvez également utiliser @RequestBody Map<String, String> params, puis utiliser params.get("key")pour obtenir la valeur du paramètre

Volonté
la source
0

Vous pouvez également utiliser une carte MultiValue pour contenir le requestBody. Voici l'exemple pour cela.

    foosId -> pathVariable
    user -> extracted from the Map of request Body 

contrairement à l'annotation @RequestBody lorsque vous utilisez une carte pour contenir le corps de la requête, nous devons annoter avec @RequestParam

et envoyez l'utilisateur dans Json RequestBody

  @RequestMapping(value = "v1/test/foos/{foosId}", method = RequestMethod.POST, headers = "Accept=application"
            + "/json",
            consumes = MediaType.APPLICATION_JSON_UTF8_VALUE ,
            produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    @ResponseBody
    public String postFoos(@PathVariable final Map<String, String> pathParam,
            @RequestParam final MultiValueMap<String, String> requestBody) {
        return "Post some Foos " + pathParam.get("foosId") + " " + requestBody.get("user");
    }
anayagam
la source