Spring MVC: วัตถุที่ซับซ้อนเป็น GET @RequestParam


192

สมมติว่าฉันมีหน้าเว็บที่แสดงรายการวัตถุบนตารางและฉันจำเป็นต้องใส่ฟอร์มเพื่อกรองตาราง ตัวกรองถูกส่งเป็น Ajax GET ไปยัง URL เช่นนั้น: http://foo.com/system/controller/action?page=1&prop1=x&prop2=y&prop3=z

และแทนที่จะมีพารามิเตอร์จำนวนมากในตัวควบคุมของฉันชอบ:

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

และสมมติว่าฉันมี MyObject เป็น:

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

    //Getters and setters
    ...
}

ฉันต้องการทำสิ่งที่ชอบ:

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

เป็นไปได้ไหม? ฉันจะทำสิ่งนั้นได้อย่างไร


1
ลองใช้ '@ModelAttribute' รวมกับ '@RequestMapping' ซึ่ง MyObject จะเป็นแบบจำลองของคุณ
Michal

@michal +1 นี่คือคู่ของบทเรียนที่แสดงให้เห็นว่าจะทำอย่างไรที่: ฤดูใบไม้ผลิ 3 MVC: การจัดการรูปแบบในฤดูใบไม้ผลิ 3.0 MVC , อะไรคือสิ่งที่และวิธีการใช้งาน@ModelAttribute , ฤดูใบไม้ผลิ MVC แบบฟอร์มการจัดการตัวอย่าง เพียงแค่ google " Spring MVC form handling " และคุณจะได้รับบทเรียน / ตัวอย่างมากมาย แต่อย่าลืมใช้วิธีการจัดการรูปแบบที่ทันสมัยเช่น Spring v2.5 +
informatik01

มีประโยชน์ด้วย: มีอะไร@ModelAttributeใน Spring MVC
informatik01

คำตอบ:


249

คุณสามารถทำสิ่งนั้นได้อย่างแท้จริงเพียงแค่ลบ@RequestParamหมายเหตุประกอบ Spring จะผูกมัดพารามิเตอร์คำขอของคุณกับอินสแตนซ์ของคลาสอย่างเรียบร้อย

public @ResponseBody List<MyObject> myAction(
    @RequestParam(value = "page", required = false) int page,
    MyObject myObject)

8
Biju, คุณสามารถยกตัวอย่างวิธีเรียกได้ไหม? ฉันกำลังเรียก http GET พื้นฐานไปที่ Rest API และไม่มีรูปแบบแฟนซี
bschandramohan

33
โปรดทราบว่า Spring ตามค่าเริ่มต้นผู้เรียกใช้ getters / setters สำหรับ MyObject เพื่อผูกมันโดยอัตโนมัติ นอกจากนี้มันจะไม่ผูก myObject
aka_sh

20
คุณจะตั้งค่าฟิลด์เป็นฟิลด์เสริมหรือไม่ใส่ก็ได้ใน MyObject ไม่แน่ใจว่าจะหาเอกสารที่เหมาะสมสำหรับการนี้ ..
worldsayshi

6
@Biju มีวิธีควบคุมค่าเริ่มต้นและจำเป็นสำหรับวิธีMyObjectนั้นในแบบที่เราสามารถทำได้กับ @RequestParam หรือไม่?
Alexey

7
@BijuKunjummen ฉันจะอัปเดตMyObjectพารามิเตอร์เพื่อยอมรับพารามิเตอร์การสืบค้นในกรณีที่เป็นงูและแมปกับสมาชิกของกรณีอูฐMyObjectได้อย่างไร ตัวอย่างเช่น?book_id=4ควรแมปกับbookIdสมาชิกของMyObject?
Vivek Vardhan

55

ฉันจะเพิ่มตัวอย่างสั้น ๆ จากฉัน

คลาส 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);
    }
}

ร้องขอการแมปภายในคลาสคอนโทรลเลอร์:

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

ค้นหา:

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

ผลลัพธ์:

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

ฉันหวังว่ามันจะช่วย :)

อัปเดต / KOTLIN

เพราะปัจจุบันฉันทำงานกับ Kotlin มากถ้าใครอยากจะนิยาม DTO ที่คล้ายกันคลาสใน Kotlin ควรมีแบบฟอร์มต่อไปนี้:

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

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

ด้วยdataชั้นเรียนเช่นนี้

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

Spring (ทดสอบใน Boot) จะส่งกลับข้อผิดพลาดต่อไปนี้สำหรับคำขอที่กล่าวถึงในคำตอบ:

"ไม่สามารถแปลงค่าประเภท 'java.lang.String []' เป็นประเภทที่ต้องการ 'java.lang.Long []'; ข้อยกเว้นแบบซ้อนคือ java.lang.NumberFormatException: สำหรับสตริงอินพุต: \" 353,234 \ ""

คลาสข้อมูลจะใช้งานได้เฉพาะกับฟอร์มขอพารามิเตอร์ต่อไปนี้

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

ระวังสิ่งนี้!


1
เป็นไปได้หรือไม่ที่จะตั้งค่า "ต้อง" เป็นฟิลด์ dto
ปกติ

4
ฉันแนะนำให้ลองใช้เครื่องมือตรวจสอบสปริง MVC ตัวอย่าง: codejava.net/frameworks/spring/ …
Przemek Nowak

อยากรู้ว่านี่ไม่จำเป็นต้องมีคำอธิบายประกอบ! ฉันสงสัยว่ามีคำอธิบายประกอบที่ชัดเจนสำหรับเรื่องนี้แม้ว่าจะไม่จำเป็นหรือไม่?
James Watkins

8

ฉันมีปัญหาที่คล้ายกันมาก ที่จริงแล้วปัญหามันลึกกว่าที่ฉันคิด ฉันใช้ jquery $.postซึ่งใช้Content-Type:application/x-www-form-urlencoded; charset=UTF-8เป็นค่าเริ่มต้น น่าเสียดายที่ฉันใช้ระบบของฉันในเรื่องนั้นและเมื่อฉันต้องการวัตถุที่ซับซ้อนเนื่องจาก@RequestParamฉันไม่สามารถทำให้มันเกิดขึ้นได้

ในกรณีของฉันฉันกำลังพยายามส่งการตั้งค่าของผู้ใช้ด้วยสิ่งที่ชอบ;

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

ในฝั่งไคลเอ็นต์ข้อมูลดิบจริงที่ส่งไปยังเซิร์ฟเวอร์คือ;

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

แยกวิเคราะห์เป็น;

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

และฝั่งเซิร์ฟเวอร์คือ;

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

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

ฉันพยายาม@ModelAttributeเพิ่ม setter / getters ตัวสร้างที่มีความเป็นไปได้ทั้งหมดUserPreferencesแต่ไม่มีโอกาสที่จะรับรู้ข้อมูลที่ส่งเป็น 5 พารามิเตอร์ แต่อันที่จริงวิธีการแมปมีเพียง 2 พารามิเตอร์เท่านั้น ฉันยังลองใช้วิธีแก้ปัญหาของ Biju ด้วยเช่นกัน แต่สิ่งที่เกิดขึ้นคือสปริงสร้างวัตถุ UserPreferences พร้อมตัวสร้างเริ่มต้นและไม่กรอกข้อมูล

ฉันแก้ไขปัญหาโดยการส่งสตริง JSon ของการตั้งค่าจากฝั่งไคลเอ็นต์และจัดการมันราวกับว่ามันเป็นสตริงที่ฝั่งเซิร์ฟเวอร์

ลูกค้า:

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

เซิร์ฟเวอร์:

@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();
        }
}

โดยย่อฉันทำการแปลงด้วยตนเองภายในวิธี REST ในความคิดของฉันเหตุผลที่ว่าทำไมฤดูใบไม้ผลิไม่รู้จักข้อมูลที่ส่งเป็นประเภทเนื้อหา


1
ฉันเพิ่งพบปัญหาเดียวกันและแก้ไขในลักษณะเดียวกัน โดยวิธีการที่คุณหาทางออกที่ดีกว่า ณ วันนี้?
Shay Elkayam

1
ฉันกำลังมีปัญหาเดียวกันเหมือนกัน ฉันใช้วิธีแก้ปัญหาที่สะอาดกว่าโดยใช้@RequestMapping(method = POST, path = "/settings/{projectId}") public void settings(@PathVariable String projectId, @RequestBody ProjectSettings settings)
Petr Újezdský

4

เนื่องจากคำถามเกี่ยวกับวิธีการตั้งค่าฟิลด์บังคับปรากฏขึ้นภายใต้โพสต์แต่ละฉันเขียนตัวอย่างเล็ก ๆ เกี่ยวกับวิธีการตั้งค่าฟิลด์ตามที่ต้องการ:

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

0

ในขณะที่คำตอบที่อ้างถึง@ModelAttribute, @RequestParam, @PathParamและชอบที่ถูกต้องมีขนาดเล็ก gotcha ฉันวิ่งเข้าไป พารามิเตอร์เมธอดที่ได้คือพร็อกซีที่ Spring ล้อมรอบ DTO ของคุณ ดังนั้นหากคุณพยายามที่จะใช้มันในบริบทที่ต้องการประเภทที่คุณกำหนดเองคุณอาจได้รับผลลัพธ์ที่ไม่คาดคิด

ต่อไปนี้จะไม่ทำงาน:

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

com.fasterxml.jackson.databind.exc.InvalidDefinitionExceptionในกรณีของฉันพยายามที่จะใช้ในแจ็คสันผูกพันผลในการ

คุณจะต้องสร้างวัตถุใหม่จาก dto


0

ใช่คุณสามารถทำได้ด้วยวิธีง่ายๆ ดูโค้ดด้านล่าง

URL - http: // localhost: 8080 / รับ / ขอ / หลาย / param / โดย / 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);
    } 

0

คำตอบที่ได้รับการยอมรับใช้งานได้อย่างมีเสน่ห์ แต่ถ้าวัตถุมีรายการของวัตถุมันจะไม่ทำงานอย่างที่คาดไว้ดังนั้นนี่คือทางออกของฉันหลังจากการขุด

ทำตามคำแนะนำหัวข้อนี้นี่คือวิธีที่ฉันได้ทำ

  • Frontend: ทำให้วัตถุของคุณเป็นสตริงกว่าเข้ารหัสใน base64 เพื่อการส่ง
  • แบ็กเอนด์: ถอดรหัสสตริงเบส 64 แล้วแปลงสตริงเจสันเป็นวัตถุที่ต้องการ

มันไม่ดีที่สุดสำหรับการดีบั๊ก API ของคุณกับบุรุษไปรษณีย์ แต่ใช้งานได้ตามที่คาดไว้สำหรับฉัน

วัตถุดั้งเดิม: {หน้า: 1, ขนาด: 5, ตัวกรอง: [{field: "id", ค่า: 1, การเปรียบเทียบ: "EQ"}

วัตถุที่เข้ารหัส: eyJwYWdlIjoxLCJzaXplIjo1LCJmaWx0ZXJzIjpbeyJmaWVsZCI6ImlkUGFyZW50IiwiYtc HTCF6XXJIX

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

และที่นี่ SearchFilterDTO และ 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
)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.