简体   繁体   English

Spring MVC:复杂对象作为 GET @RequestParam

[英]Spring MVC: Complex object as GET @RequestParam

Suppose i have a page that lists the objects on a table and i need to put a form to filter the table.假设我有一个页面列出了表格上的对象,我需要放置一个表格来过滤表格。 The filter is sent as an Ajax GET to an URL like that: http://foo.com/system/controller/action?page=1&prop1=x&prop2=y&prop3=z过滤器作为 Ajax GET 发送到这样的 URL: http : //foo.com/system/controller/action?page=1&prop1= x& prop2=y&prop3=z

And instead of having lots of parameters on my Controller like:而不是在我的控制器上有很多参数,比如:

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

And supposing i have MyObject as:假设我有 MyObject 为:

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

    //Getters and setters
    ...
}

I wanna do something like:我想做这样的事情:

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

Is it possible?是否有可能? How can i do that?我怎样才能做到这一点?

You can absolutely do that, just remove the @RequestParam annotation, Spring will cleanly bind your request parameters to your class instance:您绝对可以这样做,只需删除@RequestParam注释,Spring 会将您的请求参数干净地绑定到您的类实例:

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

I will add some short example from me.我将添加一些来自我的简短示例。

The DTO class: 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);
    }
}

Request mapping inside controller class:在控制器类中请求映射:

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

Query:询问:

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

Result:结果:

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

I hope it helps :)我希望它有帮助:)

UPDATE / KOTLIN更新/科特林

Because currently I'm working a lot of with Kotlin if someone wants to define similar DTO the class in Kotlin should have the following form:因为目前我正在使用 Kotlin 进行很多工作,如果有人想定义类似的 DTO,那么 Kotlin 中的类应该具有以下形式:

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

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

With the data class like this one:使用这样的data类:

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

the Spring (tested in Boot) returns the following error for request mentioned in answer: Spring(在 Boot 中测试)为回答中提到的请求返回以下错误:

"Failed to convert value of type 'java.lang.String[]' to required type 'java.lang.Long[]'; nested exception is java.lang.NumberFormatException: For input string: \\"353,234\\"" “无法将类型 'java.lang.String[]' 的值转换为所需的类型 'java.lang.Long[]';嵌套异常为 java.lang.NumberFormatException:对于输入字符串:\\"353,234\\""

The data class will work only for the following request params form:数据类仅适用于以下请求参数表单:

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

Be aware of this!请注意这一点!

Since the question on how to set fields mandatory pops up under each post, I wrote a small example on how to set fields as required:由于每个帖子下都会弹出如何设置必填字段的问题,我写了一个关于如何设置必填字段的小例子:

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

//Add this to your rest controller class
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String testComplexObject (@Valid ExampleDTO e){
    System.out.println(e.getMandatoryParam() + " " + e.getTestDate());
    return "Does this work?";
}

I have a very similar problem.我有一个非常相似的问题。 Actually the problem is deeper as I thought.其实问题比我想象的更深。 I am using jquery $.post which uses Content-Type:application/x-www-form-urlencoded; charset=UTF-8我正在使用 jquery $.post它使用Content-Type:application/x-www-form-urlencoded; charset=UTF-8 Content-Type:application/x-www-form-urlencoded; charset=UTF-8 as default.默认Content-Type:application/x-www-form-urlencoded; charset=UTF-8 Unfortunately I based my system on that and when I needed a complex object as a @RequestParam I couldn't just make it happen.不幸的是,我的系统基于此,当我需要一个复杂的对象作为@RequestParam我不能让它发生。

In my case I am trying to send user preferences with something like;在我的情况下,我试图用类似的东西发送用户偏好;

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

On client side the actual raw data sent to the server is;在客户端,发送到服务器的实际原始数据是;

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

parsed as;解析为;

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

and the server side is;服务器端是;

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

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

I tried @ModelAttribute , added setter/getters, constructors with all possibilities to UserPreferences but no chance as it recognized the sent data as 5 parameters but in fact the mapped method has only 2 parameters.我尝试了@ModelAttribute ,向UserPreferences添加了具有所有可能性的 setter/getter、构造函数,但没有机会,因为它将发送的数据识别为 5 个参数,但实际上映射的方法只有 2 个参数。 I also tried Biju's solution however what happens is that, spring creates an UserPreferences object with default constructor and doesn't fill in the data.我也尝试了 Biju 的解决方案,但是发生的情况是,spring 创建了一个带有默认构造函数的 UserPreferences 对象,并且不填充数据。

I solved the problem by sending JSon string of the preferences from the client side and handle it as if it is a String on the server side;我通过从客户端发送首选项的 JSon 字符串并在服务器端将其作为字符串处理来解决该问题;

client:客户:

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

to brief, I did the conversion manually inside the REST method.简而言之,我在 REST 方法中手动进行了转换。 In my opinion the reason why spring doesn't recognize the sent data is the content-type.在我看来,spring 无法识别发送的数据的原因是内容类型。

While answers that refer to @ModelAttribute , @RequestParam , @PathParam and the likes are valid, there is a small gotcha I ran into.虽然引用@ModelAttribute@RequestParam@PathParam等的答案是有效的,但我遇到了一个小问题。 The resulting method parameter is a proxy that Spring wraps around your DTO.生成的方法参数是 Spring 包裹在 DTO 周围的代理。 So, if you attempt to use it in a context that requires your own custom type, you may get some unexpected results.因此,如果您尝试在需要您自己的自定义类型的上下文中使用它,您可能会得到一些意想不到的结果。

The following will not work:以下将不起作用:

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

In my case, attempting to use it in Jackson binding resulted in a com.fasterxml.jackson.databind.exc.InvalidDefinitionException .就我而言,尝试在 Jackson 绑定中使用它会导致com.fasterxml.jackson.databind.exc.InvalidDefinitionException

You will need to create a new object from the dto.您将需要从 dto 创建一个新对象。

Yes, You can do it in a simple way.是的,您可以通过一种简单的方式做到这一点。 See below code of lines.请参阅下面的行代码。

URL - http://localhost:8080/get/request/multiple/param/by/map?name= 'abc' & id='123' 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);
    } 

Accepted answer works like a charm but if the object has a list of objects it won't work as expected so here is my solution after some digging.接受的答案就像一个魅力,但如果对象有一个对象列表,它就不会按预期工作,所以这是我经过一些挖掘后的解决方案。

Following this thread advice, here is how I've done.按照这个线程建议,这是我的做法。

  • Frontend: stringify your object than encode it in base64 for submission.前端:将您的对象字符串化而不是将其编码为 base64 以提交。
  • Backend: decode base64 string then convert the string json into desired object.后端:解码 base64 字符串,然后将字符串 json 转换为所需的对象。

It isn't the best for debugging your API with postman but it is working as expected for me.它不是用邮递员调试 API 的最佳选择,但它按我的预期工作。

Original object: { page: 1, size: 5, filters: [{ field: "id", value: 1, comparison: "EQ" }原始对象: {页面:1,大小:5,过滤器:[{字段:“id”,值:1,比较:“EQ”}

Encoded object: eyJwYWdlIjoxLCJzaXplIjo1LCJmaWx0ZXJzIjpbeyJmaWVsZCI6ImlkUGFyZW50IiwiY29tcGFyaXNvbiI6Ik5VTEwifV19编码对象: 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)
}

And here the SearchFilterDTO and FilterDTO这里是 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
)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM