Meneruskan beberapa variabel di @RequestBody ke pengontrol MVC Spring menggunakan Ajax

113

Apakah perlu untuk membungkus objek pendukung? Aku ingin melakukan ini:

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

Dan gunakan JSON seperti ini:

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

Tapi sebaliknya saya harus menggunakan:

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

Dan kemudian gunakan JSON ini:

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

Apakah itu benar? Pilihan saya yang lain adalah mengubah RequestMethodmenjadi GETdan digunakan @RequestParamdalam string kueri atau menggunakan @PathVariablekeduanya RequestMethod.

NimChimpsky
sumber

Jawaban:

92

Anda benar, parameter beranotasi @RequestBody diharapkan untuk menampung seluruh isi permintaan dan mengikat ke satu objek, jadi pada dasarnya Anda harus menggunakan opsi Anda.

Jika Anda benar-benar menginginkan pendekatan Anda, ada implementasi khusus yang dapat Anda lakukan:

Katakan ini json Anda:

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

dan Anda ingin mengikatnya ke dua parameter di sini:

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

Pertama-tama tentukan anotasi khusus, misalnya @JsonArg, dengan jalur JSON seperti jalur ke informasi yang Anda inginkan:

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

Sekarang tulis Custom HandlerMethodArgumentResolver yang menggunakan JsonPath yang ditentukan di atas untuk menyelesaikan argumen sebenarnya:

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

    }
}

Sekarang daftarkan saja ini dengan Spring MVC. Sedikit terlibat, tetapi ini akan berfungsi dengan baik.

Biju Kunjummen
sumber
2
Bagaimana cara membuat anotasi kustom, katakan @JsonArg?
Surendra Jnawali
Kenapa ini? sekarang saya harus membuat banyak kelas pembungkus yang berbeda di backend. Saya memigrasi aplikasi Struts2 ke Springboot dan ada banyak kasus di mana objek JSON yang dikirim menggunakan ajax sebenarnya adalah dua atau lebih objek model: misalnya Pengguna dan Aktivitas
Jose Ospina
tautan ini menunjukkan kepada Anda "cara mendaftarkan ini dengan Spring MVC" geekabyte.blogspot.sg/2014/08/…
Bodil
3
masih menarik mengapa opsi ini tidak ditambahkan ke pegas. Sepertinya pilihan yang logis ketika Anda memiliki seperti 2 rindu dan tidak ingin membuat objek pembungkus untuk itu
tibi
@SurendraJnawali Anda dapat melakukannya seperti ini@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface JsonArg { String value() default ""; }
Epono
88

Meskipun benar bahwa @RequestBodyharus memetakan ke satu objek, objek itu bisa menjadi a Map, jadi ini memberi Anda cara yang baik untuk apa yang Anda coba capai (tidak perlu menulis satu objek pendukung):

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

Anda juga dapat mengikat ObjectNode Jackson jika Anda menginginkan pohon JSON lengkap:

public boolean getTest(@RequestBody ObjectNode json) {
   //json.get("str1").asText() == "test one"
Kong
sumber
@JoseOspina mengapa tidak bisa melakukannya. Risiko apa pun yang terkait dengan Map <String, Object> dengan requestBody
Ben Cheng
@ Ben Maksud saya, Anda dapat menggunakan SATU Mapobjek tunggal untuk menyimpan sejumlah objek di dalamnya, tetapi objek tingkat atas harus tetap hanya satu, tidak boleh ada dua objek tingkat atas.
Jose Ospina
1
Saya pikir sisi negatif dari pendekatan dinamis seperti Map<String, String>: Perpustakaan dokumentasi API (kesombongan / springfox dll) mungkin tidak akan dapat mengurai skema permintaan / tanggapan Anda dari kode sumber Anda.
stratovarius
10

Anda dapat mencampur argumen posting dengan menggunakan variabel body dan path untuk tipe data yang lebih sederhana:

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

Untuk melewatkan banyak objek, params, variabel, dan sebagainya. Anda dapat melakukannya secara dinamis menggunakan ObjectNode dari perpustakaan jackson sebagai parameter Anda. Anda bisa melakukannya seperti ini:

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

Saya berharap bantuan ini.

azwar_akbar
sumber
2

@RequestParamadalah parameter HTTP GETor yang POSTdikirim oleh klien, pemetaan permintaan adalah segmen URL yang variabel:

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

var1& var2adalah parameter permintaan.

http:/host/form/{params}

{params}adalah pemetaan permintaan. Anda dapat menghubungi layanan Anda seperti: http:/host/form/useratau di http:/host/form/firm mana perusahaan & pengguna digunakan sebagai Pathvariable.

psisodia
sumber
ini tidak menjawab pertanyaan dan salah, Anda tidak menggunakan string kueri dengan permintaan POST
NimChimpsky
1
@NimChimpsky: tentu Anda bisa. Permintaan POST masih dapat menyertakan parameter di URL.
Martijn Pieters
2

Solusi mudahnya adalah dengan membuat kelas muatan yang memiliki atribut str1 dan str2:

@Getter
@Setter
public class ObjHolder{

String str1;
String str2;

}

Dan setelah Anda bisa lewat

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

dan isi permintaan Anda adalah:

{
    "str1": "test one",
    "str2": "two test"
}
Nbenz
sumber
1
Apa paket penjelasan ini? Impor otomatis hanya menawarkan impor jdk.nashorn.internal.objects.annotations.Setter; EDIT. Saya menganggapnya Lombok projectlombok.org/features/GetterSetter . Harap perbaiki saya jika saya salah
Gleichmut
@Gleichmut Anda dapat menggunakan getter dan setter sederhana untuk variabel Anda. Ini akan bekerja seperti yang Anda harapkan.
Gimnath
1

Alih-alih menggunakan json, Anda dapat melakukan hal yang sederhana.

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

Sekarang di pengontrol Anda perlu memetakan permintaan ajax seperti di bawah ini:

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

Semoga ini bisa membantu Anda.

Jepang Trivedi
sumber
1
Itu json, dan tidak berhasil. Anda menentukan requestparam dalam metode, tetapi mendefinisikan equestbody dengan json di permintaan posting ajax.
NimChimpsky
Lihat Saya belum pernah menggunakan format JSON dalam panggilan ajax. Saya hanya menggunakan dua parameter permintaan dan di pengontrol kita bisa mendapatkan parameter tersebut dengan penjelasan @RequestParam. Ini bekerja. Saya menggunakan ini. Coba saja.
Japan Trivedi
Saya sudah mencobanya, itu inti dari pertanyaannya. Tidak bisa seperti itu.
NimChimpsky
Tentukan apa yang sebenarnya Anda coba. Tunjukkan itu dalam pertanyaan Anda. Saya pikir Anda memiliki persyaratan yang berbeda dari apa yang saya pahami.
Japan Trivedi
1
Bekerja untuk saya pada percobaan pertama. Terima kasih!
Humppakäräjät
1

Saya telah mengadaptasi solusi 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;

    }

}

Apa bedanya:

  • Saya menggunakan Jackson untuk mengonversi json
  • Saya tidak memerlukan nilai dalam anotasi, Anda dapat membaca nama parameter dari MethodParameter
  • Saya juga membaca jenis parameter dari Methodparameter => jadi solusinya harus generik (saya mengujinya dengan string dan DTO)

BR

pengguna3227576
sumber
0

parameter permintaan ada untuk GET dan POST, Untuk Dapatkan itu akan ditambahkan sebagai string kueri ke URL tetapi untuk POST itu ada dalam Badan Permintaan

Kaleem
sumber
0

Tidak yakin di mana Anda menambahkan json tetapi jika saya melakukannya seperti ini dengan sudut itu berfungsi tanpa 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
sumber
0

Baik. Saya sarankan membuat Value Object (Vo) yang berisi bidang yang Anda butuhkan. Kode ini lebih sederhana, kami tidak mengubah fungsi Jackson dan bahkan lebih mudah untuk dipahami. Salam!

Matias Zamorano
sumber
0

Anda dapat mencapai apa yang Anda inginkan dengan menggunakan @RequestParam. Untuk ini, Anda harus melakukan hal berikut:

  1. Deklarasikan parameter RequestParams yang mewakili objek Anda dan setel required opsi ke false jika Anda ingin dapat mengirim nilai null.
  2. Di frontend, rangkai objek yang ingin Anda kirim dan sertakan sebagai parameter permintaan.
  3. Di bagian belakang, putar string JSON kembali ke objek yang mereka wakili menggunakan Jackson ObjectMapper atau sesuatu seperti itu, dan voila!

Saya tahu, ini sedikit hack tetapi berhasil! ;)

Maurice
sumber
0

Anda juga bisa pengguna @RequestBody Map<String, String> params, lalu gunakan params.get("key")untuk mendapatkan nilai parameter

Akan
sumber
0

Anda juga dapat menggunakan Peta MultiValue untuk menampung requestBody. Berikut adalah contohnya.

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

tidak seperti anotasi @RequestBody saat menggunakan Peta untuk menampung isi permintaan, kita perlu memberi anotasi dengan @RequestParam

dan mengirim pengguna ke 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.dll
sumber