Передача нескольких переменных в @RequestBody контроллеру Spring MVC с использованием Ajax

113

Обязательно ли оборачивать объект подложки? Я хочу сделать это:

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

И используйте такой JSON:

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

Но вместо этого я должен использовать:

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

А затем используйте этот JSON:

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

Это правильно? Мой другой вариант был бы изменить , RequestMethodчтобы GETи использовать @RequestParamв строке запроса или использования @PathVariableлибо с RequestMethod.

НимЧимпский
источник

Ответы:

92

Вы правы, ожидается, что аннотированный параметр @RequestBody будет содержать все тело запроса и привязываться к одному объекту, поэтому вам, по сути, придется использовать свои параметры.

Если вам абсолютно нужен ваш подход, есть индивидуальная реализация, которую вы можете сделать:

Скажите, что это ваш json:

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

и вы хотите привязать его к двум параметрам здесь:

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

Сначала определите настраиваемую аннотацию, скажем @JsonArg, с путем JSON, например путем к нужной информации:

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

Теперь напишите Custom HandlerMethodArgumentResolver, который использует JsonPath, определенный выше, для разрешения фактического аргумента:

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

    }
}

Теперь просто зарегистрируйте это с помощью Spring MVC. Немного запутано, но это должно работать чисто.

Биджу Кунджуммен
источник
2
Как мне создать собственную аннотацию, например @JsonArg, пожалуйста?
Сурендра Джнавали
Почему это? теперь мне нужно создать много разных классов-оболочек в бэкэнде. Я переношу приложение Struts2 на Springboot, и у него было много случаев, когда объекты JSON, отправленные с использованием ajax, на самом деле являются двумя или более объектами модели: например, пользователь и действие
Хосе Оспина
эта ссылка покажет вам, «как зарегистрировать это с помощью Spring MVC» geekabyte.blogspot.sg/2014/08/…
Bodil,
3
До сих пор интересно, почему этот вариант не добавлен к весне. это кажется логичным вариантом, когда у вас есть 2 лонга и вы не хотите создавать для них объект-оболочку
tibi
@SurendraJnawali, вы можете сделать это вот так@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JsonArg { String value() default ""; }
Epono
88

Хотя это правда, что он @RequestBodyдолжен соответствовать одному объекту, этот объект может быть a Map, поэтому это дает вам хороший способ достичь того, чего вы пытаетесь достичь (нет необходимости писать одноразовый объект поддержки):

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

Вы также можете привязаться к ObjectNode Джексона, если хотите получить полное дерево JSON:

public boolean getTest(@RequestBody ObjectNode json) {
   //json.get("str1").asText() == "test one"
Kong
источник
@JoseOspina, почему не могу этого сделать. Любой риск, связанный с Map <String, Object> с requestBody
Бен Ченг
@Ben Я имею в виду, что вы можете использовать ОДИН единственный Mapобъект для хранения любого количества объектов внутри него, но объект верхнего уровня по-прежнему должен быть только один, не может быть двух объектов верхнего уровня.
Jose Ospina
1
Я думаю, что обратная сторона динамического подхода Map<String, String>: библиотеки документации API (swagger / springfox и т. Д.), Вероятно, не смогут проанализировать вашу схему запроса / ответа из вашего исходного кода.
stratovarius
10

Вы можете перепутать аргумент post, используя переменные body и path для более простых типов данных:

@RequestMapping(value = "new-trade/portfolio/{portfolioId}", method = RequestMethod.POST)
    public ResponseEntity<List<String>> newTrade(@RequestBody Trade trade, @PathVariable long portfolioId) {
...
}
сорокопут
источник
10

Для передачи нескольких объектов, параметров, переменных и т. Д. Вы можете сделать это динамически, используя ObjectNode из библиотеки Джексона в качестве параметра. Сделать это можно так:

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

Надеюсь, это поможет.

azwar_akbar
источник
2

@RequestParam- параметр HTTP GETили, POSTотправленный клиентом, отображение запроса - это сегмент URL-адреса, переменная которого:

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

var1& var2являются параметрами запроса.

http:/host/form/{params}

{params}отображение запроса. Вы можете позвонить в службу поддержки, например: http:/host/form/userили http:/host/form/firm где используются фирма и пользователь Pathvariable.

псизодия
источник
это не отвечает на вопрос и неверно, вы не используете строку запроса с запросами POST
NimChimpsky
1
@NimChimpsky: конечно, можешь. Запрос POST по-прежнему может включать параметры в URL-адрес.
Martijn Pieters
2

Простое решение - создать класс полезной нагрузки с атрибутами str1 и str2:

@Getter
@Setter
public class ObjHolder{

String str1;
String str2;

}

И после того, как ты сможешь пройти

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

и тело вашего запроса:

{
    "str1": "test one",
    "str2": "two test"
}
Nbenz
источник
1
Что это за аннотации? Автоимпорт предлагал только импорт jdk.nashorn.internal.objects.annotations.Setter; РЕДАКТИРОВАТЬ. Я предполагаю, что это Lombok projectlombok.org/features/GetterSetter . Пожалуйста, поправьте меня, если я ошибаюсь
Гляйхмут
@Gleichmut вы можете использовать простые геттеры и сеттеры для своих переменных. Он будет работать так, как вы ожидаете.
Гимнат,
1

Вместо использования json вы можете сделать простую вещь.

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

Теперь в контроллере вам нужно сопоставить запрос ajax, как показано ниже:

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

Надеюсь, это тебе поможет.

Япония Триведи
источник
1
Это json, и он не работает. Вы указываете requestparam в методе, но определяете equestbody с помощью json в почтовом запросе ajax.
NimChimpsky
Смотрите, я не использовал формат JSON в вызове ajax. Я просто использовал два параметра запроса, и в контроллере мы можем получить эти параметры с помощью аннотации @RequestParam. Это работает. Я использую это. Просто попробуйте.
Япония Триведи
Я пробовал это, это суть вопроса. Так не работает.
NimChimpsky
Укажите, пожалуйста, что именно вы пробовали. Покажите это в своем вопросе. Я думаю, что у вас другие требования, чем я понял.
Япония Триведи
1
Сработало у меня с первого раза. Спасибо!
Humppakäräjät,
1

Я адаптировал решение Биджу:

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;

    }

}

В чем разница:

  • Я использую Джексон для преобразования json
  • Мне не нужно значение в аннотации, вы можете прочитать имя параметра из MethodParameter
  • Я также прочитал тип параметра из Methodparameter =>, поэтому решение должно быть общим (я тестировал его со строкой и DTO)

BR

user3227576
источник
0

Параметр запроса существует как для GET, так и для POST, для Get он будет добавлен как строка запроса к URL-адресу, но для POST он находится в теле запроса

Калим
источник
0

Не уверен, где вы добавляете json, но если я сделаю это так с angular, он будет работать без requestBody: angluar:

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

Ява:

@PostMapping(URL_MATCH)
public ResponseEntity<Void> match(Long str1, Long str2) {
  log.debug("found: {} and {}", str1, str2);
}
голень
источник
0

Хорошо. Я предлагаю создать объект-значение (Vo), содержащий нужные вам поля. Код проще, мы не меняем работу Джексона и его еще проще понять. С уважением!

Матиас Заморано
источник
0

Вы можете добиться того, чего хотите, используя @RequestParam. Для этого вам необходимо сделать следующее:

  1. Объявите параметры RequestParams, представляющие ваши объекты, и установите для requiredпараметра значение false, если вы хотите иметь возможность отправлять нулевое значение.
  2. На интерфейсе настройте объекты, которые вы хотите отправить, и включите их в качестве параметров запроса.
  3. На бэкэнде превратите строки JSON обратно в объекты, которые они представляют, используя Jackson ObjectMapper или что-то в этом роде, и вуаля!

Я знаю, это немного похоже на взлом, но он работает! ;)

Морис
источник
0

вы также можете @RequestBody Map<String, String> paramsиспользовать его params.get("key")для получения значения параметра

Будет
источник
0

Вы также можете использовать MultiValue Map для хранения requestBody внутри. Вот пример для этого.

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

в отличие от аннотации @RequestBody при использовании карты для хранения тела запроса, которое нам нужно аннотировать с помощью @RequestParam

и отправьте пользователя в 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");
    }
анаягам
источник