Przekazywanie wielu zmiennych w @RequestBody do kontrolera Spring MVC przy użyciu Ajax

113

Czy konieczne jest zawijanie obiektu podkładowego? Chce to zrobić:

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

I użyj JSON w ten sposób:

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

Ale zamiast tego muszę użyć:

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

A następnie użyj tego JSON:

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

Czy to jest poprawne? Moja druga opcja byłoby zmienić RequestMethod, aby GETi wykorzystanie @RequestParamw ciągu kwerendy lub korzystania @PathVariablez albo RequestMethod.

NimChimpsky
źródło

Odpowiedzi:

92

Masz rację, parametr @RequestBody z adnotacjami powinien przechowywać całą treść żądania i wiązać się z jednym obiektem, więc zasadniczo będziesz musiał przejść z opcjami.

Jeśli absolutnie chcesz swoje podejście, istnieje niestandardowa implementacja, którą możesz zrobić:

Powiedz, że to Twój plik json:

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

i chcesz powiązać go z dwoma parametrami tutaj:

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

Najpierw zdefiniuj niestandardową adnotację, powiedzmy @JsonArg, ze ścieżką JSON, taką jak ścieżka do żądanych informacji:

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

Teraz napisz Custom HandlerMethodArgumentResolver, który używa zdefiniowanej powyżej JsonPath do rozwiązania rzeczywistego argumentu:

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

    }
}

Teraz po prostu zarejestruj to w Spring MVC. Trochę zawiłe, ale powinno to działać czysto.

Biju Kunjummen
źródło
2
Jak utworzyć niestandardową adnotację, powiedz @JsonArg, proszę?
Surendra Jnawali,
Dlaczego to? teraz muszę utworzyć wiele różnych klas opakowujących na zapleczu. Migruję aplikację Struts2 do Springboot i było wiele przypadków, w których obiekty JSON wysyłane przy użyciu ajax są w rzeczywistości dwoma lub więcej obiektami modelu: np. Użytkownik i aktywność
Jose Ospina
ten link pokazuje "jak zarejestrować to w Spring MVC" geekabyte.blogspot.sg/2014/08/…
Bodil
3
wciąż ciekawe, dlaczego ta opcja nie jest dodawana do wiosny. wydaje się to logiczną opcją, gdy masz jakieś 2 longs i nie chcesz tworzyć dla tego obiektu opakowującego
tibi
@SurendraJnawali, możesz to zrobić w ten sposób@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JsonArg { String value() default ""; }
Epono
88

Chociaż prawdą jest, że @RequestBodymusi być mapowany na pojedynczy obiekt, ten obiekt może być a Map, więc daje to dobry sposób na to, co próbujesz osiągnąć (nie ma potrzeby zapisywania pojedynczego obiektu zapasowego):

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

Możesz także powiązać się z ObjectNode Jacksona, jeśli chcesz mieć pełne drzewo JSON:

public boolean getTest(@RequestBody ObjectNode json) {
   //json.get("str1").asText() == "test one"
Kong
źródło
@JoseOspina, dlaczego nie może tego zrobić. Wszelkie ryzyko związane z Map <String, Object> z requestBody
Ben Cheng,
@Ben Mam na myśli, że możesz użyć JEDNEGO pojedynczego Mapobiektu do przechowywania dowolnej liczby obiektów w nim, ale obiekt najwyższego poziomu nadal musi być tylko jeden, nie może być dwóch obiektów najwyższego poziomu.
Jose Ospina,
1
Myślę, że wadą takiego podejścia dynamicznegoMap<String, String> jest to, że biblioteki dokumentacji API (swagger / springfox itp.) Prawdopodobnie nie będą w stanie przeanalizować schematu żądania / odpowiedzi z kodu źródłowego.
stratovarius
10

Możesz pomieszać argument post, używając zmiennej body i path dla prostszych typów danych:

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

Do przekazywania wielu obiektów, parametrów, zmiennych i tak dalej. Możesz to zrobić dynamicznie, używając ObjectNode z biblioteki jackson jako swojego parametru. Możesz to zrobić w ten sposób:

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

Mam nadzieję, że to pomoże.

azwar_akbar
źródło
2

@RequestParamto parametr HTTP GETlub POSTwysyłany przez klienta, mapowanie żądań to segment adresu URL, którego zmienna:

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

var1i var2są parametrami żądania.

http:/host/form/{params}

{params}jest mapowaniem żądań. możesz zadzwonić do swojej usługi na przykład: http:/host/form/userlub http:/host/form/firm gdzie firma i użytkownik są używane jako Pathvariable.

psisodia
źródło
to nie odpowiada na pytanie i jest błędne, nie używasz ciągu zapytania z żądaniami POST
NimChimpsky
1
@ NimChimpsky: oczywiście, że możesz. Żądanie POST nadal może zawierać parametry w adresie URL.
Martijn Pieters
2

Prostym rozwiązaniem jest utworzenie klasy ładunku, która ma atrybuty str1 i str2:

@Getter
@Setter
public class ObjHolder{

String str1;
String str2;

}

A po przejściu

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

a treść Twojej prośby to:

{
    "str1": "test one",
    "str2": "two test"
}
Nbenz
źródło
1
Jaki jest pakiet tych adnotacji? Autoimport oferował tylko import jdk.nashorn.internal.objects.annotations.Setter; EDYTOWAĆ. Zakładam, że to Lombok projectlombok.org/features/GetterSetter . Proszę mnie poprawić, jeśli się mylę
Gleichmut
@Gleichmut, możesz użyć prostych metod pobierających i ustawiających dla swoich zmiennych. Będzie działać zgodnie z oczekiwaniami.
Gimnath
1

Zamiast używać json, możesz zrobić prostą rzecz.

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

Teraz w kontrolerze musisz zmapować żądanie Ajax, jak poniżej:

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

Mam nadzieję, że to ci pomoże.

Japan Trivedi
źródło
1
To jest json i nie działa. Podajesz requestparam w metodzie, ale definiujesz equestbody z json w żądaniu postu w AJAX.
NimChimpsky
Zobacz Nie korzystałem z formatu JSON w wywołaniu ajax. Po prostu użyłem dwóch parametrów żądania i w kontrolerze możemy je pobrać z adnotacją @RequestParam. To działa. Używam tego. Po prostu daj temu szansę.
Japan Trivedi
Próbowałem tego, to jest kwestia quesiton. To tak nie działa.
NimChimpsky
Proszę określić, co dokładnie próbowałeś. Pokaż to w swoim pytaniu. Myślę, że masz inne wymagania niż to, co zrozumiałem.
Japan Trivedi
1
Udało mi się za pierwszym razem. Dzięki!
Humppakäräjät
1

Dostosowałem rozwiązanie 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;

    }

}

Jaka jest różnica:

  • Używam Jacksona do konwersji json
  • Nie potrzebuję wartości w adnotacji, możesz odczytać nazwę parametru z MethodParameter
  • Odczytałem również typ parametru z Methodparameter =>, więc rozwiązanie powinno być generyczne (przetestowałem to z ciągiem i DTO)

BR

user3227576
źródło
0

parametr żądania istnieje zarówno dla GET, jak i POST, w przypadku polecenia Get zostanie dołączony jako ciąg zapytania do adresu URL, ale w przypadku POST znajduje się w treści żądania

Kaleem
źródło
0

Nie jestem pewien, gdzie dodasz json, ale jeśli zrobię to w ten sposób z angularem, działa bez requestBody: angluar:

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

Jawa:

@PostMapping(URL_MATCH)
public ResponseEntity<Void> match(Long str1, Long str2) {
  log.debug("found: {} and {}", str1, str2);
}
tibi
źródło
0

Dobry. Proponuję utworzyć obiekt wartości (Vo) zawierający potrzebne pola. Kod jest prostszy, nie zmieniamy działania Jacksona i jest jeszcze łatwiejszy do zrozumienia. Pozdrowienia!

Matias Zamorano
źródło
0

Możesz osiągnąć to, co chcesz, używając @RequestParam. W tym celu wykonaj następujące czynności:

  1. Zadeklaruj parametry RequestParams, które reprezentują twoje obiekty i ustaw requiredopcję na false, jeśli chcesz mieć możliwość wysyłania wartości null.
  2. Na frontendu określ obiekty, które chcesz wysłać i dołącz je jako parametry żądania.
  3. Na zapleczu zamień ciągi JSON z powrotem w obiekty, które reprezentują, używając Jackson ObjectMapper lub czegoś podobnego, i voila!

Wiem, to trochę hack, ale działa! ;)

Maurice
źródło
0

możesz także @RequestBody Map<String, String> paramsużyć użytkownika , a następnie użyj, params.get("key")aby uzyskać wartość parametru

Będzie
źródło
0

Możesz również użyć mapy MultiValue do przechowywania requestBody. Oto przykład na to.

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

w przeciwieństwie do adnotacji @RequestBody, gdy używamy mapy do przechowywania treści żądania, musimy dodać adnotację za pomocą @RequestParam

i wyślij użytkownika w 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
źródło