Passaggio di più variabili in @RequestBody a un controller Spring MVC utilizzando Ajax


113

È necessario avvolgere un oggetto di supporto? Voglio farlo:

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

E usa un JSON come questo:

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

Ma invece devo usare:

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

E poi usa questo JSON:

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

È corretto? La mia altra opzione sarebbe quella di cambiare il RequestMethodverso GETe l'uso @RequestParamin stringa di query o l'uso @PathVariablecon entrambi RequestMethod.

Risposte:


92

Hai ragione, il parametro annotato @RequestBody dovrebbe contenere l'intero corpo della richiesta e collegarsi a un oggetto, quindi essenzialmente dovrai andare con le tue opzioni.

Se vuoi assolutamente il tuo approccio, c'è un'implementazione personalizzata che puoi fare però:

Dì che questo è il tuo json:

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

e vuoi associarlo ai due parametri qui:

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

Definisci prima un'annotazione personalizzata, ad esempio @JsonArg, con il percorso JSON come il percorso per le informazioni che desideri:

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

Ora scrivi un HandlerMethodArgumentResolver personalizzato che utilizza il JsonPath definito sopra per risolvere l'argomento effettivo:

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

    }
}

Ora basta registrarlo con Spring MVC. Un po 'complicato, ma dovrebbe funzionare in modo pulito.


2
Come faccio a creare un'annotazione personalizzata, ad esempio @JsonArg, per favore?
Surendra Jnawali

Perchè è questo? ora devo creare molte classi wrapper diverse nel backend. Sto migrando un'applicazione Struts2 a Springboot e ci sono stati molti casi in cui gli oggetti JSON inviati utilizzando ajax sono in realtà due o più oggetti del modello: ad esempio un utente e un'attività
Jose Ospina

questo link mostra "come registrarlo con Spring MVC" geekabyte.blogspot.sg/2014/08/…
Bodil

3
ancora interessante perché questa opzione non viene aggiunta alla primavera. sembra un'opzione logica quando hai tipo 2 long e non vuoi creare un oggetto wrapper per esso
tibi

@SurendraJnawali puoi farlo in questo modo@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JsonArg { String value() default ""; }
Epono

88

Sebbene sia vero che @RequestBodydeve essere mappato a un singolo oggetto, quell'oggetto può essere un Map, quindi questo ti offre un buon modo per ciò che stai tentando di ottenere (non è necessario scrivere un oggetto di supporto una tantum):

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

Puoi anche eseguire il binding all'ObjectNode di Jackson se desideri un albero JSON completo:

public boolean getTest(@RequestBody ObjectNode json) {
   //json.get("str1").asText() == "test one"

@JoseOspina why can't do so. Qualsiasi rischio associato a Map <String, Object> con requestBody
Ben Cheng

@ Ben voglio dire che puoi usare UN singolo Mapoggetto per memorizzare un numero qualsiasi di oggetti al suo interno, ma l'oggetto di livello superiore deve essere ancora uno solo, non possono esserci due oggetti di livello superiore.
Jose Ospina

1
Penso che lo svantaggio di un approccio dinamico come Map<String, String>è: le librerie di documentazione API (swagger / springfox ecc.) Probabilmente non saranno in grado di analizzare lo schema di richiesta / risposta dal codice sorgente.
stratovarius

10

Puoi mescolare l'argomento del post utilizzando la variabile body e path per tipi di dati più semplici:

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

10

Per passare più oggetti, parametri, variabili e così via. Puoi farlo dinamicamente utilizzando ObjectNode dalla libreria jackson come parametro. Puoi farlo in questo modo:

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

Spero che questo aiuto.


2

@RequestParamè il parametro HTTP GETo POSTinviato dal client, la mappatura della richiesta è un segmento di URL la cui variabile:

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

var1e var2sono parametri di richiesta.

http:/host/form/{params}

{params}è una mappatura delle richieste. potresti chiamare il tuo servizio come: http:/host/form/usero http:/host/form/firm dove azienda e utente vengono utilizzati come Pathvariable.


questo non risponde alla domanda ed è sbagliato, non usi una stringa di query con richieste POST
NimChimpsky

1
@NimChimpsky: certo che puoi. Una richiesta POST può ancora includere parametri nell'URL.
Martijn Pieters

2

La soluzione facile è creare una classe di payload che abbia str1 e str2 come attributi:

@Getter
@Setter
public class ObjHolder{

String str1;
String str2;

}

E dopo puoi passare

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

e il corpo della tua richiesta è:

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

1
Qual è il pacchetto di queste annotazioni? Autoimport offriva solo import jdk.nashorn.internal.objects.annotations.Setter; MODIFICARE. Presumo che sia Lombok projectlombok.org/features/GetterSetter . Per favore correggimi se sbaglio
Gleichmut

@Gleichmut puoi usare semplici getter e setter per le tue variabili. Funzionerà come ti aspetti.
Gimnath

1

Invece di usare json, puoi fare cose semplici.

$.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.>
                });

Ora nel controller è necessario mappare la richiesta ajax come di seguito:

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

Spero che questo ti aiuti.


1
Questo è json e non funziona. Stai specificando requestparam nel metodo, ma definendo equestbody con json nella richiesta post ajax.
NimChimpsky

Vedi Non ho usato il formato JSON nella chiamata ajax. Ho semplicemente usato due parametri di richiesta e nel controller possiamo ottenere quei parametri con l'annotazione @RequestParam. Funziona. Io uso questo. Basta fare un tentativo.
Giappone Trivedi

L'ho provato, è il punto della domanda. Non funziona così.
NimChimpsky

Per favore specifica cosa hai provato esattamente. Dimostralo nella tua domanda. Penso che tu abbia requisiti diversi da quelli che ho capito.
Giappone Trivedi

1
Ha funzionato per me al primo tentativo. Grazie!
Humppakäräjät

1

Ho adattato la soluzione di 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;

    }

}

Qual è la differenza:

  • Sto usando Jackson per convertire JSON
  • Non ho bisogno di un valore nell'annotazione, puoi leggere il nome del parametro da MethodParameter
  • Ho anche letto il tipo del parametro dal Methodparameter => quindi la soluzione dovrebbe essere generica (l'ho testata con string e DTO)

BR


0

il parametro di richiesta esiste sia per GET che per POST, per Get verrà aggiunto come stringa di query all'URL ma per POST è all'interno del corpo della richiesta


0

Non sono sicuro di dove aggiungi il json ma se lo faccio in questo modo con angular funziona senza la richiestaBody: angluar:

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

Giava:

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

0

Buona. Suggerisco di creare un oggetto valore (Vo) che contenga i campi di cui hai bisogno. Il codice è più semplice, non cambiamo il funzionamento di Jackson ed è ancora più facile da capire. Saluti!


0

Puoi ottenere ciò che desideri utilizzando @RequestParam. Per questo dovresti fare quanto segue:

  1. Dichiarare i parametri RequestParams che rappresentano i propri oggetti e impostare il required opzione su false se si desidera essere in grado di inviare un valore null.
  2. Sul frontend, stringa gli oggetti che desideri inviare e includili come parametri della richiesta.
  3. Sul back-end, trasforma le stringhe JSON negli oggetti che rappresentano usando Jackson ObjectMapper o qualcosa del genere, e voilà!

Lo so, è un po 'un trucco ma funziona! ;)


0

puoi anche @RequestBody Map<String, String> paramsusare l' utente , quindi utilizzare params.get("key")per ottenere il valore del parametro


0

Puoi anche utilizzare una mappa MultiValue per contenere requestBody. Ecco un esempio.

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

a differenza dell'annotazione @RequestBody quando si utilizza una mappa per contenere il corpo della richiesta, dobbiamo annotare con @RequestParam

e invia l'utente in 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");
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.