Передача декількох змінних у @RequestBody до контролера MVC Spring за допомогою 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?
Surendra Jnawali

Чому це? тепер я маю створити багато різних класів обгортки в бекенді. Я мігрую додаток Struts2 до Springboot, і в ньому було дуже багато випадків, коли об’єкти JSON, надіслані за допомогою ajax, насправді є двома або більше об’єктами моделі: наприклад, користувач та активність
Jose Ospina

посилання показати вам «як зареєструвати це з 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потрібно зіставляти один об'єкт, цей об'єкт може бути символом 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"

@JoseOspina чому не може цього зробити. Будь-який ризик, пов’язаний із картою <Рядок, об’єкт> із запитомБоді
Бен Чен

@ Я маю на увазі, що ви можете використовувати ОДИН єдиний Mapоб'єкт для зберігання будь-якої кількості об'єктів всередині нього, але об'єкт верхнього рівня все одно повинен бути лише одним, не може бути двох об'єктів верхнього рівня.
Хосе Оспіна

1
Я думаю, що недоліком такого динамічного підходу Map<String, String>є: бібліотеки документації API (swagger / springfox тощо), ймовірно, не зможуть розібрати вашу схему запиту / відповіді з вашого вихідного коду.
стратоварій

10

Ви можете змішати аргумент пост, використовуючи змінну тіла та шляху для більш простих типів даних:

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

Я сподіваюся, що це допоможе.


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

1
Який пакет цих анотацій? Автоімпорт пропонував лише імпорт jdk.nashorn.internal.objects.annotations.Setter; EDIT. Я припускаю, що це проект Lombok projektlombok.org/features/GetterSetter . Будь ласка, виправте мене, якщо я помиляюся
Gleichmut

@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


0

параметр запиту існує як для GET, так і для POST, For Get він додаватиметься як рядок запиту до URL, але для POST він знаходиться в тілі запиту


0

Не впевнений, куди ви додаєте json, але якщо я це роблю так з кутовим, він працює без запиту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

Добре. Я пропоную створити об'єкт значення (Vo), який містить потрібні вам поля. Код простіший, ми не змінюємо функціонування Джексона і це ще простіше зрозуміти. З повагою!


0

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

  1. Оголосіть параметри RequestParams, які представляють ваші об'єкти, і встановіть required параметр false, якщо ви хочете мати можливість надіслати нульове значення.
  2. На фронтальному етапі оберіть об'єкти, які ви хочете надіслати, і включіть їх у параметри запиту
  3. Поверніть рядки JSON назад в об'єкти, які вони представляють, використовуючи Jackson ObjectMapper або щось подібне, і вуаля!

Я знаю, це трохи хак, але це працює! ;)


0

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


0

Ви також можете використовувати карту MultiValue для утримання запитуBody. Ось приклад для цього.

    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");
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.