Spring MVC: Đối tượng phức tạp là GET @RequestParam


191

Giả sử tôi có một trang liệt kê các đối tượng trên một bảng và tôi cần đặt một biểu mẫu để lọc bảng. Bộ lọc được gửi dưới dạng Ajax GET tới một URL như thế: http://foo.com/system/controll/action?page=1&prop1=x&prop2=y&prop3=z

Và thay vì có nhiều tham số trên Bộ điều khiển của tôi như:

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

Và giả sử tôi có MyObject là:

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

    //Getters and setters
    ...
}

Tôi muốn làm một cái gì đó như:

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

Có thể không? Làm thế nào tôi có thể làm điều đó?


1
Hãy thử sử dụng '@ModelAttribution' kết hợp với '@RequestMapping', trong đó MyObject sẽ là mô hình của bạn.
michal

@michal +1. Dưới đây là một vài hướng dẫn chỉ ra cách thực hiện: Spring 3 MVC: Xử lý biểu mẫu trong Spring 3.0 MVC , Cái gì và cách sử dụng@ModelAttribute , Ví dụ xử lý biểu mẫu Spring MVC . Chỉ cần google " Xử lý biểu mẫu MVC mùa xuân " và bạn sẽ nhận được rất nhiều hướng dẫn / ví dụ. Nhưng hãy chắc chắn sử dụng cách hiện đại của xử lý hình thức, tức là mùa xuân v2.5 +
informatik01

Câu trả lời:


247

Bạn hoàn toàn có thể làm điều đó, chỉ cần xóa @RequestParamchú thích, Spring sẽ liên kết sạch các tham số yêu cầu của bạn với thể hiện của lớp:

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

8
Biju, bạn có thể cho một ví dụ về cách gọi này? Tôi đang thực hiện một cuộc gọi http GET cơ bản tới Rest API và nó không có hình thức ưa thích nào.
bschandramohan

32
Lưu ý rằng Spring theo mặc định yêu cầu getters / setters cho MyObject để liên kết nó tự động. Mặt khác, nó sẽ không ràng buộc myObject.
aka_sh

20
Làm cách nào bạn có thể đặt các trường là tùy chọn / không tùy chọn trong MyObject? Không chắc chắn làm thế nào để tìm tài liệu phù hợp cho việc này ..
worldsayshi

6
@Biju, Có cách nào để kiểm soát các giá trị mặc định và được yêu cầu MyObjectsau đó, theo cách tương tự chúng ta có thể làm với @RequestParam không?
Alexey

7
@BijuKunjummen Làm cách nào tôi có thể cập nhật MyObjectđể chấp nhận tham số truy vấn trong trường hợp Snake và ánh xạ nó tới thành viên trường hợp lạc đà của MyObject. Ví dụ, ?book_id=4nên được ánh xạ với bookIdthành viên của MyObject?
Vivek Vardhan

55

Tôi sẽ thêm một số ví dụ ngắn từ tôi.

Lớp 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);
    }
}

Yêu cầu ánh xạ bên trong lớp trình điều khiển:

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

Truy vấn:

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

Kết quả:

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

Tôi hy vọng nó sẽ giúp :)

CẬP NHẬT / KOTLINE

Bởi vì hiện tại tôi đang làm việc rất nhiều với Kotlin nếu ai đó muốn định nghĩa DTO tương tự, lớp trong Kotlin phải có dạng sau:

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

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

Với datalớp học như thế này:

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

Spring (được kiểm tra trong Boot) trả về lỗi sau cho yêu cầu được đề cập trong câu trả lời:

"Không thể chuyển đổi giá trị của loại 'java.lang.String []' thành loại bắt buộc 'java.lang.Long []'; ngoại lệ lồng nhau là java.lang.NumberFormatException: Đối với chuỗi đầu vào: \" 353,234 \ ""

Lớp dữ liệu sẽ chỉ hoạt động cho mẫu params yêu cầu sau:

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

Hãy nhận ra điều này!


1
có thể đặt "bắt buộc" cho các trường dto không?
Bình thường

4
Tôi đề nghị thử với trình xác nhận Spring MVC. Ví dụ: codejava.net/frameworks/spring/ từ
Przemek Nowak

Thật tò mò rằng điều này không cần chú thích! Tôi tự hỏi, có một chú thích rõ ràng cho điều này mặc dù không cần thiết?
James Watkins

8

Tôi có một vấn đề rất tương tự. Thật ra vấn đề sâu sắc hơn tôi nghĩ. Tôi đang sử dụng jquery $.postmà sử dụng Content-Type:application/x-www-form-urlencoded; charset=UTF-8như mặc định. Thật không may, tôi dựa vào hệ thống của mình và khi tôi cần một đối tượng phức tạp như một@RequestParam tôi không thể làm cho nó xảy ra.

Trong trường hợp của tôi, tôi đang cố gắng gửi tùy chọn người dùng với một cái gì đó như;

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

Về phía khách hàng, dữ liệu thô thực sự được gửi đến máy chủ là;

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

phân tích như;

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

và phía máy chủ là;

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

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

Tôi đã thử @ModelAttribute, thêm setter / getters, constructor với tất cả các khả năng đểUserPreferences nhưng không có cơ hội vì nó nhận ra dữ liệu đã gửi là 5 tham số nhưng thực tế phương thức được ánh xạ chỉ có 2 tham số. Tôi cũng đã thử giải pháp của biju tuy nhiên điều xảy ra là, mùa xuân tạo ra một đối tượng UserPreferences với hàm tạo mặc định và không điền dữ liệu.

Tôi đã giải quyết vấn đề bằng cách gửi chuỗi JSon của các tùy chọn từ phía máy khách và xử lý nó như thể đó là một Chuỗi ở phía máy chủ;

khách hàng

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

người phục vụ:

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

nói ngắn gọn, tôi đã thực hiện chuyển đổi thủ công bên trong phương thức REST. Theo tôi, lý do tại sao mùa xuân không nhận ra dữ liệu đã gửi là loại nội dung.


1
Tôi vừa trải qua vấn đề tương tự, và giải quyết nó theo cùng một cách. Nhân tiện, bạn đã tìm thấy một giải pháp tốt hơn như ngày hôm nay?
Shay Elkayam

1
Tôi cũng có vấn đề chính xác như vậy. Tôi đã sử dụng cách giải quyết khác bằng cách sử dụng@RequestMapping(method = POST, path = "/settings/{projectId}") public void settings(@PathVariable String projectId, @RequestBody ProjectSettings settings)
Petr Újezdský

4

Vì câu hỏi về cách đặt các trường bắt buộc bật lên dưới mỗi bài đăng, tôi đã viết một ví dụ nhỏ về cách đặt các trường theo yêu cầu:

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

Trong khi câu trả lời đề cập đến @ModelAttribute, @RequestParam.@PathParam và những cái tên như là hợp lệ, có một Gotcha nhỏ tôi chạy vào. Tham số phương thức kết quả là một proxy mà Spring bao bọc xung quanh DTO của bạn. Vì vậy, nếu bạn cố gắng sử dụng nó trong ngữ cảnh yêu cầu loại tùy chỉnh của riêng bạn, bạn có thể nhận được một số kết quả không mong muốn.

Những điều sau đây sẽ không hoạt động:

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

Trong trường hợp của tôi, cố gắng sử dụng nó trong ràng buộc Jackson đã dẫn đến một com.fasterxml.jackson.databind.exc.InvalidDefinitionException.

Bạn sẽ cần tạo một đối tượng mới từ dto.


0

Vâng, bạn có thể làm điều đó một cách đơn giản. Xem mã dưới đây của dòng.

URL - http: // localhost: 8080 / get / request / many / 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);
    } 

0

Câu trả lời được chấp nhận hoạt động như một lá bùa nhưng nếu đối tượng có một danh sách các đối tượng thì nó sẽ không hoạt động như mong đợi, vì vậy đây là giải pháp của tôi sau một số hoạt động đào.

Theo lời khuyên chủ đề này , đây là cách tôi đã làm.

  • Frontend: xâu chuỗi đối tượng của bạn hơn là mã hóa nó trong base64 để gửi.
  • Phần cuối: giải mã chuỗi base64 sau đó chuyển chuỗi json thành đối tượng mong muốn.

Nó không phải là tốt nhất để gỡ lỗi API của bạn với người đưa thư nhưng nó hoạt động như mong đợi đối với tôi.

Đối tượng gốc: {page: 1, kích thước: 5, bộ lọc: [{trường: "id", giá trị: 1, so sánh: "EQ"}

Đối tượng được mã hóa: eyJwYWdlIjoxLCJzaXplIjo1LCJmaWx0ZXJzIjpbeyJmaWVsZCI6ImlkUGFyZW50IiwiY29tcGFyaXNvbiI6Ik5VTE

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

Và đây là SearchFilterDTO và 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
)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.