Übergeben mehrerer Variablen in @RequestBody an einen Spring MVC-Controller mit Ajax


111

Ist es notwendig, ein Hintergrundobjekt einzuwickeln? Ich möchte das machen:

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

Und verwenden Sie einen JSON wie folgt:

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

Aber stattdessen muss ich verwenden:

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

Und dann benutze diesen JSON:

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

Ist das korrekt? Meine andere Möglichkeit wäre, die Änderungen RequestMethodzu GETund Verwendung @RequestParamin Query - String oder Verwendung @PathVariableentweder mit RequestMethod.

Antworten:


92

Sie haben Recht, es wird erwartet, dass der mit @RequestBody annotierte Parameter den gesamten Hauptteil der Anforderung enthält und an ein Objekt bindet, sodass Sie im Wesentlichen mit Ihren Optionen arbeiten müssen.

Wenn Sie Ihren Ansatz unbedingt wollen, gibt es eine benutzerdefinierte Implementierung, die Sie jedoch ausführen können:

Sagen Sie, das ist Ihr json:

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

und Sie möchten es hier an die beiden Parameter binden:

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

Definieren Sie zunächst eine benutzerdefinierte Anmerkung, z. B. @JsonArgmit dem JSON-Pfad wie dem Pfad zu den gewünschten Informationen:

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

Schreiben Sie nun einen benutzerdefinierten HandlerMethodArgumentResolver, der den oben definierten JsonPath verwendet , um das eigentliche Argument aufzulösen:

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

    }
}

Registrieren Sie dies jetzt einfach bei Spring MVC. Ein bisschen kompliziert, aber das sollte sauber funktionieren.


2
Wie erstelle ich eine benutzerdefinierte Anmerkung, sagen Sie bitte @JsonArg?
Surendra Jnawali

Warum ist das? Jetzt muss ich im Backend viele verschiedene Wrapper-Klassen erstellen. Ich migriere eine Struts2-Anwendung nach Springboot und es gab viele Fälle, in denen JSON-Objekte, die mit Ajax gesendet wurden, tatsächlich zwei oder mehr Objekte des Modells sind: z. B. ein Benutzer und eine Aktivität
Jose Ospina

Dieser Link zeigt Ihnen, wie Sie dies bei Spring MVC registrieren können. geekabyte.blogspot.sg/2014/08/…
Bodil

3
Es ist immer noch interessant, warum diese Option nicht zum Frühling hinzugefügt wird. Es scheint eine logische Option zu sein, wenn Sie 2 Longs haben und kein Wrapper-Objekt dafür erstellen möchten
Tibi

@ SurendraJnawali Sie können es so machen@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JsonArg { String value() default ""; }
Epono

86

Es ist zwar richtig, dass @RequestBodyein Objekt einem einzelnen Objekt zugeordnet werden muss, aber dieses Objekt kann ein Objekt sein. Auf Mapdiese Weise erhalten Sie einen guten Weg zu dem, was Sie erreichen möchten (Sie müssen kein einmaliges Hintergrundobjekt schreiben):

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

Sie können auch an Jacksons ObjectNode binden, wenn Sie einen vollständigen JSON-Baum wünschen:

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

@ JoseOspina warum kann das nicht. Jedes mit Map <String, Object> verbundene Risiko mit requestBody
Ben Cheng

@Ben Ich meine, Sie können EIN einzelnes MapObjekt verwenden, um eine beliebige Anzahl von Objekten darin zu speichern, aber das Objekt der obersten Ebene muss immer noch nur eines sein, es können nicht zwei Objekte der obersten Ebene vorhanden sein.
Jose Ospina

1
Ich denke, der Nachteil eines dynamischen Ansatzes Map<String, String>ist: API-Dokumentationsbibliotheken (swagger / springfox usw.) können Ihr Anforderungs- / Antwortschema wahrscheinlich nicht aus Ihrem Quellcode analysieren.
Stratovarius

10

Sie können das Post-Argument verwechseln, indem Sie für einfachere Datentypen body- und path-Variablen verwenden:

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

10

Zum Übergeben mehrerer Objekte, Parameter, Variablen usw. Sie können dies dynamisch tun, indem Sie ObjectNode aus der Jackson-Bibliothek als Parameter verwenden. Sie können es so machen:

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

Ich hoffe diese Hilfe.


2

@RequestParamist der vom Client gesendete Parameter HTTP GEToder. Die POSTAnforderungszuordnung ist ein Segment der URL, dessen Variable:

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

var1& var2sind Anforderungsparameter.

http:/host/form/{params}

{params}ist eine Anforderungszuordnung. Sie können Ihren Dienst wie folgt anrufen: http:/host/form/useroder http:/host/form/firm wo Firma & Benutzer als verwendet werden Pathvariable.


Dies beantwortet die Frage nicht und ist falsch. Sie verwenden keine
Abfragezeichenfolge

1
@NimChimpsky: Sicher kannst du. Eine POST-Anforderung kann weiterhin Parameter in der URL enthalten.
Martijn Pieters

2

Die einfache Lösung besteht darin, eine Nutzlastklasse zu erstellen, deren Attribute str1 und str2 sind:

@Getter
@Setter
public class ObjHolder{

String str1;
String str2;

}

Und nachdem Sie passieren können

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

und der Hauptteil Ihrer Anfrage ist:

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

1
Was ist das Paket dieser Anmerkungen? Autoimport bot nur Import jdk.nashorn.internal.objects.annotations.Setter an; BEARBEITEN. Ich nehme an, es ist Lombok projectlombok.org/features/GetterSetter . Bitte korrigieren Sie mich, wenn ich falsch
liege

@Gleichmut Sie können einfache Getter und Setter für Ihre Variablen verwenden. Es wird so funktionieren, wie Sie es erwarten.
Gimnath

1

Anstatt json zu verwenden, können Sie auch einfache Dinge tun.

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

Jetzt müssen Sie im Controller die Ajax-Anforderung wie folgt zuordnen:

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

Hoffe das hilft dir.


1
Das ist json und es funktioniert nicht. Sie geben requestparam in method an, definieren aber equestbody mit json in der Ajax-Post-Anfrage.
NimChimpsky

Siehe Ich habe das JSON-Format im Ajax-Aufruf nicht verwendet. Ich habe einfach zwei Anforderungsparameter verwendet und im Controller können wir diese Parameter mit der Annotation @RequestParam abrufen. Es funktioniert. Ich benutze das. Probieren Sie es einfach aus.
Japan Trivedi

Ich habe das versucht, es ist der Punkt der Frage. So funktioniert das nicht.
NimChimpsky

Bitte geben Sie an, was genau Sie versucht haben. Zeigen Sie das in Ihrer Frage. Ich denke, Sie haben andere Anforderungen als ich verstanden habe.
Japan Trivedi

1
Hat beim ersten Versuch für mich gearbeitet. Vielen Dank!
Humppakäräjät

1

Ich habe die Lösung von Biju angepasst:

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;

    }

}

Was ist der Unterschied:

  • Ich benutze Jackson, um Json zu konvertieren
  • Ich brauche keinen Wert in der Annotation, Sie können den Namen des Parameters aus dem MethodParameter lesen
  • Ich habe auch den Typ des Parameters aus dem Methodenparameter = = gelesen, daher sollte die Lösung generisch sein (ich habe sie mit String und DTOs getestet).

BR


0

Der Anforderungsparameter ist sowohl für GET als auch für POST vorhanden. Für Get wird er als Abfragezeichenfolge an die URL angehängt, für POST befindet er sich jedoch im Anforderungshauptteil


0

Ich bin mir nicht sicher, wo du den JSON hinzufügst, aber wenn ich es so mit Winkel mache, funktioniert es ohne die Anfrage. Body: 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);
}

0

Gut. Ich schlage vor, ein Wertobjekt (Vo) zu erstellen, das die benötigten Felder enthält. Der Code ist einfacher, wir ändern die Funktionsweise von Jackson nicht und es ist noch einfacher zu verstehen. Grüße!


0

Sie können erreichen, was Sie wollen, indem Sie verwenden @RequestParam. Dazu sollten Sie Folgendes tun:

  1. Deklarieren Sie die RequestParams-Parameter, die Ihre Objekte darstellen, und legen Sie die fest required Option auf false, wenn Sie einen Nullwert senden möchten.
  2. Stringifizieren Sie im Frontend die Objekte, die Sie senden möchten, und fügen Sie sie als Anforderungsparameter hinzu.
  3. Verwandeln Sie im Backend die JSON-Zeichenfolgen mit Jackson ObjectMapper oder ähnlichem wieder in die Objekte, die sie darstellen, und voila!

Ich weiß, es ist ein bisschen ein Hack, aber es funktioniert! ;)


0

Sie können auch Benutzer verwenden @RequestBody Map<String, String> paramsund dann verwenden params.get("key"), um den Wert des Parameters abzurufen


0

Sie können auch eine MultiValue-Map verwenden, um den requestBody zu speichern. Hier ist das Beispiel dafür.

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

Im Gegensatz zur Annotation @RequestBody, wenn eine Map zum Speichern des Anforderungshauptteils verwendet wird, müssen Annotationen mit @RequestParam erfolgen

und senden Sie den Benutzer im 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");
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.