Spring MVC: Objek kompleks sebagai GET @RequestParam

192

Misalkan saya memiliki halaman yang mencantumkan objek pada tabel dan saya perlu meletakkan formulir untuk memfilter tabel. Filter dikirim sebagai Ajax GET ke URL seperti itu: http://foo.com/system/controller/action?page=1&prop1=x&prop2=y&prop3=z

Dan bukannya memiliki banyak parameter pada Kontroler saya seperti:

@RequestMapping(value = "/action")
public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    @RequestParam(value = "prop1", required = false) String prop1,
    @RequestParam(value = "prop2", required = false) String prop2,
    @RequestParam(value = "prop3", required = false) String prop3) { ... }

Dan seandainya saya memiliki MyObject sebagai:

public class MyObject {
    private String prop1;
    private String prop2;
    private String prop3;

    //Getters and setters
    ...
}

Saya ingin melakukan sesuatu seperti:

@RequestMapping(value = "/action")
public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    @RequestParam(value = "myObject", required = false) MyObject myObject,) { ... }

Apa itu mungkin? Bagaimana saya bisa melakukan itu?

renanleandrof
sumber
1
Coba gunakan '@ModelAttribute' yang dikombinasikan dengan '@RequestMapping', di mana MyObject akan menjadi model Anda.
michal
@Michal +1. Berikut adalah beberapa tutorial yang menunjukkan cara melakukannya: Spring 3 MVC: Formulir Penanganan di Spring 3.0 MVC , Apa dan bagaimana cara menggunakan@ModelAttribute , Contoh Penanganan Formulir Spring MVC . Hanya google " Spring MVC form handling " dan Anda akan mendapatkan banyak tutorial / contoh. Tetapi pastikan untuk menggunakan cara modern penanganan formulir, yaitu Spring v2.5 +
informatik01
Juga berguna: Apa yang ada @ModelAttributedi Spring MVC
informatik01

Jawaban:

249

Anda benar-benar dapat melakukan itu, cukup hapus @RequestParamanotasi, Spring akan dengan rapi mengikat parameter permintaan Anda ke instance kelas Anda:

public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    MyObject myObject)
Biju Kunjummen
sumber
8
Biju, dapatkah Anda memberi contoh cara menyebutnya? Saya membuat panggilan GET http dasar ke API Istirahat dan tidak memiliki formulir mewah.
bschandramohan
33
Perhatikan bahwa Pegas secara default getquers / setters untuk MyObject mengikatnya secara otomatis. Selain itu, itu tidak akan mengikat myObject.
aka_sh
20
Bagaimana Anda bisa mengatur bidang opsional / non-opsional di MyObject? Tidak yakin bagaimana menemukan dokumentasi yang tepat untuk ini ..
worldsayshi
6
@ Biju, Apakah ada cara untuk mengontrol nilai-nilai default dan diperlukan untuk MyObjectsaat itu, dengan cara serupa yang bisa kita lakukan dengan @RequestParam?
Alexey
7
@BijuKunjummen Bagaimana saya bisa memperbarui MyObjectuntuk menerima parameter kueri dalam case Snake dan memetakannya ke anggota case unta MyObject. Misalnya, ?book_id=4harus dipetakan dengan bookIdanggota MyObject?
Vivek Vardhan
55

Saya akan menambahkan beberapa contoh singkat dari saya.

Kelas DTO:

public class SearchDTO {
    private Long id[];

    public Long[] getId() {
        return id;
    }

    public void setId(Long[] id) {
        this.id = id;
    }
    // reflection toString from apache commons
    @Override
    public String toString() {
        return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
    }
}

Meminta pemetaan di dalam kelas pengontrol:

@RequestMapping(value="/handle", method=RequestMethod.GET)
@ResponseBody
public String handleRequest(SearchDTO search) {
    LOG.info("criteria: {}", search);
    return "OK";
}

Pertanyaan:

http://localhost:8080/app/handle?id=353,234

Hasil:

[http-apr-8080-exec-7] INFO  c.g.g.r.f.w.ExampleController.handleRequest:59 - criteria: SearchDTO[id={353,234}]

Saya harap ini membantu :)

PEMBARUAN / KOTLIN

Karena saat ini saya banyak bekerja dengan Kotlin jika seseorang ingin mendefinisikan DTO serupa, kelas di Kotlin harus memiliki bentuk berikut:

class SearchDTO {
    var id: Array<Long>? = arrayOf()

    override fun toString(): String {
        // to string implementation
    }
}

Dengan datakelas seperti ini:

data class SearchDTO(var id: Array<Long> = arrayOf())

Spring (diuji dalam Booting) mengembalikan kesalahan berikut untuk permintaan yang disebutkan dalam jawaban:

"Gagal mengonversi nilai tipe 'java.lang.String []' ke tipe yang diperlukan 'java.lang.Long []'; pengecualian bersarang adalah java.lang.NumberFormatException: Untuk string input: \" 353.234 \ ""

Kelas data hanya akan berfungsi untuk formulir params permintaan berikut:

http://localhost:8080/handle?id=353&id=234

Sadarilah ini!

Przemek Nowak
sumber
1
apakah mungkin untuk menetapkan "wajib" ke bidang dto?
Normal
4
Saya sarankan untuk mencoba dengan validator Spring MVC. Contoh: codejava.net/frameworks/spring/…
Przemek Nowak
Sangat ingin tahu bahwa ini tidak memerlukan anotasi! Saya bertanya-tanya, apakah ada penjelasan eksplisit untuk ini walaupun tidak perlu?
James Watkins
8

Saya memiliki masalah yang sangat mirip. Sebenarnya masalahnya lebih dalam seperti yang saya kira. Saya menggunakan jquery $.postyang menggunakan Content-Type:application/x-www-form-urlencoded; charset=UTF-8sebagai default. Sayangnya saya mendasarkan sistem saya pada itu dan ketika saya membutuhkan objek yang kompleks sebagai @RequestParamsaya tidak bisa mewujudkannya.

Dalam kasus saya, saya mencoba mengirim preferensi pengguna dengan sesuatu seperti;

 $.post("/updatePreferences",  
    {id: 'pr', preferences: p}, 
    function (response) {
 ...

Di sisi klien, data mentah aktual yang dikirim ke server adalah;

...
id=pr&preferences%5BuserId%5D=1005012365&preferences%5Baudio%5D=false&preferences%5Btooltip%5D=true&preferences%5Blanguage%5D=en
...

diuraikan sebagai;

id:pr
preferences[userId]:1005012365
preferences[audio]:false
preferences[tooltip]:true
preferences[language]:en

dan sisi server adalah;

@RequestMapping(value = "/updatePreferences")
public
@ResponseBody
Object updatePreferences(@RequestParam("id") String id, @RequestParam("preferences") UserPreferences preferences) {

    ...
        return someService.call(preferences);
    ...
}

Saya mencoba @ModelAttribute, menambahkan setter / getter, konstruktor dengan semua kemungkinan UserPreferencestetapi tidak ada kesempatan karena mengenali data yang dikirim sebagai 5 parameter tetapi pada kenyataannya metode yang dipetakan hanya memiliki 2 parameter. Saya juga mencoba solusi Biju namun yang terjadi adalah, spring membuat objek UserPreferences dengan konstruktor default dan tidak mengisi data.

Saya memecahkan masalah dengan mengirim string JSon preferensi dari sisi klien dan menanganinya seolah-olah itu adalah String di sisi server;

klien:

 $.post("/updatePreferences",  
    {id: 'pr', preferences: JSON.stringify(p)}, 
    function (response) {
 ...

server:

@RequestMapping(value = "/updatePreferences")
public
@ResponseBody
Object updatePreferences(@RequestParam("id") String id, @RequestParam("preferences") String preferencesJSon) {


        String ret = null;
        ObjectMapper mapper = new ObjectMapper();
        try {
            UserPreferences userPreferences = mapper.readValue(preferencesJSon, UserPreferences.class);
            return someService.call(userPreferences);
        } catch (IOException e) {
            e.printStackTrace();
        }
}

Singkatnya, saya melakukan konversi secara manual di dalam metode REST. Menurut pendapat saya alasan mengapa pegas tidak mengenali data yang dikirim adalah tipe konten.

hevi
sumber
1
Saya hanya mengalami masalah yang sama, dan menyelesaikannya dengan cara yang sama. Omong-omong, apakah Anda menemukan solusi yang lebih baik hari ini?
Shay Elkayam
1
Saya juga mengalami masalah yang sama persis. Saya melakukan solusi bersih menggunakan@RequestMapping(method = POST, path = "/settings/{projectId}") public void settings(@PathVariable String projectId, @RequestBody ProjectSettings settings)
Petr Újezdský
4

Karena pertanyaan tentang cara mengatur bidang wajib muncul di bawah setiap posting, saya menulis contoh kecil tentang cara mengatur bidang yang diperlukan:

public class ExampleDTO {
    @NotNull
    private String mandatoryParam;

    private String optionalParam;

    @DateTimeFormat(iso = ISO.DATE) //accept Dates only in YYYY-MM-DD
    @NotNull
    private LocalDate testDate;

    public String getMandatoryParam() {
        return mandatoryParam;
    }
    public void setMandatoryParam(String mandatoryParam) {
        this.mandatoryParam = mandatoryParam;
    }
    public String getOptionalParam() {
        return optionalParam;
    }
    public void setOptionalParam(String optionalParam) {
        this.optionalParam = optionalParam;
    }
    public LocalDate getTestDate() {
        return testDate;
    }
    public void setTestDate(LocalDate testDate) {
        this.testDate = testDate;
    }
}

@RequestMapping(value = "/test", method = RequestMethod.GET)
public String testComplexObject (@Valid ExampleDTO e){
    System.out.println(e.getMandatoryParam() + " " + e.getTestDate());
    return "Does this work?";
}
FluffyDestroyerOfCode
sumber
0

Sementara jawaban yang merujuk kepada @ModelAttribute, @RequestParam, @PathParamdan sejenisnya adalah valid, ada gotcha kecil aku berlari ke dalam. Parameter metode yang dihasilkan adalah proxy yang Spring membungkus DTO Anda. Jadi, jika Anda mencoba menggunakannya dalam konteks yang memerlukan jenis kustom Anda sendiri, Anda mungkin mendapatkan beberapa hasil yang tidak terduga.

Berikut ini tidak akan berfungsi:

@GetMapping(produces = APPLICATION_JSON_VALUE)
public ResponseEntity<CustomDto> request(@ModelAttribute CustomDto dto) {
    return ResponseEntity.ok(dto);
}

Dalam kasus saya, mencoba menggunakannya dalam pengikatan Jackson menghasilkan a com.fasterxml.jackson.databind.exc.InvalidDefinitionException.

Anda harus membuat objek baru dari dto.

DKO
sumber
0

Ya, Anda bisa melakukannya dengan cara yang sederhana. Lihat kode baris di bawah ini.

URL - http: // localhost: 8080 / get / request / multiple / param / by / map? Name = 'abc' & id = '123'

 @GetMapping(path = "/get/request/header/by/map")
    public ResponseEntity<String> getRequestParamInMap(@RequestParam Map<String,String> map){
        // Do your business here 
        return new ResponseEntity<String>(map.toString(),HttpStatus.OK);
    } 
vkstream
sumber
0

Jawaban yang diterima berfungsi seperti mantra tetapi jika objek memiliki daftar objek, itu tidak akan berfungsi seperti yang diharapkan jadi di sini adalah solusi saya setelah beberapa penggalian.

Mengikuti saran utas ini , berikut adalah cara saya melakukannya.

  • Frontend: meringkas objek Anda daripada menyandikannya di base64 untuk pengiriman.
  • Backend: decode string base64 lalu konversikan string json menjadi objek yang diinginkan.

Ini bukan yang terbaik untuk men-debug API Anda dengan tukang pos tetapi berfungsi seperti yang diharapkan untuk saya.

Objek asli: {halaman: 1, ukuran: 5, filter: [{bidang: "id", nilai: 1, perbandingan: "EQ"}

Objek yang disandikan : eyJwYWdlIjoxLCJzaXplIjo1LCJmaWx0ZXJzIjpbeyJmaWVsZCI6ImlkUGFyZW50IiwiY29tcGFyaXNvbiI6Ik5VTEwifV19

@GetMapping
fun list(@RequestParam search: String?): ResponseEntity<ListDTO> {
    val filter: SearchFilterDTO = decodeSearchFieldDTO(search)
    ...
}

private fun decodeSearchFieldDTO(search: String?): SearchFilterDTO {
    if (search.isNullOrEmpty()) return SearchFilterDTO()
    return Gson().fromJson(String(Base64.getDecoder().decode(search)), SearchFilterDTO::class.java)
}

Dan di sini SearchFilterDTO dan FilterDTO

class SearchFilterDTO(
    var currentPage: Int = 1,
    var pageSize: Int = 10,
    var sort: Sort? = null,
    var column: String? = null,
    var filters: List<FilterDTO> = ArrayList<FilterDTO>(),
    var paged: Boolean = true
)

class FilterDTO(
    var field: String,
    var value: Any,
    var comparison: Comparison
)
Gabriel Brito
sumber