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

Posted

技术标签:

【中文标题】Spring MVC:复杂对象作为 GET @RequestParam【英文标题】:Spring MVC: Complex object as GET @RequestParam 【发布时间】:2013-06-01 06:53:12 【问题描述】:

假设我有一个页面列出了表格上的对象,我需要放置一个表格来过滤表格。过滤器作为 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,)  ... 

有可能吗? 我该怎么做?

【问题讨论】:

@michal +1。这里有几个教程展示了如何做到这一点:Spring 3 MVC: Handling Forms in Spring 3.0 MVC、What is and how to use @ModelAttribute、Spring MVC Form Handling Example。只需谷歌“Spring MVC 表单处理”,您就会获得大量教程/示例。但一定要使用现代的表单处理方式,即 Spring v2.5+ 也有用:What is @ModelAttribute in Spring MVC 从 MyObject 中删除 @RequestParam 并定义 MyObject 的属性(通过为所有属性添加无参数构造函数和设置器)或(定义一个没有无参数构造函数的全参数构造函数,在这种情况下不需要设置器)。 【参考方案1】:

您完全可以这样做,只需删除@RequestParam 注释,Spring 将干净地将您的请求参数绑定到您的类实例:

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

【讨论】:

请注意,默认情况下 Spring 需要 getter/setter 来让 MyObject 自动绑定它。否则它不会绑定 myObject。 如何在 MyObject 中设置字段为可选/非可选?不知道如何为此找到适当的文档.. @Biju,有没有办法控制MyObject 所需的默认值,然后,我们可以用@RequestParam 做类似的方式? @BijuKunjummen 如何更新MyObject 以接受Snake case 中的查询参数并将其映射到MyObject 的camel case 成员。例如,?book_id=4,应该与MyObject 的成员bookId 映射? 我有这样的请求参数:page-numberpage-size。我的请求 DTO 对象会是什么样子?在 java 中,我无法创建名称为 page-numberpage-size 的字段。有没有办法告诉 spring 具有给定名称的请求参数应该映射到这个或那个字段?请指教。【参考方案2】:

我将添加一些我自己的简短示例。

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

注意这一点!

【讨论】:

是否可以将“必需”设置为 dto 字段? 我建议尝试使用 Spring MVC 验证器。示例:codejava.net/frameworks/spring/… 很好奇这个不需要注解!我想知道,尽管没有必要,但是否有明确的注释? 这种与 java/kotlin 的行为差异应该是一个错误。我创建了一个问题。花几个小时来搜索它为什么不起作用,但我以前在 java 中没有问题。见github.com/spring-projects/spring-framework/issues/25815 感谢 Kotlin 的方式,但是有什么方法可以让不可变对象使用 val 代替 var 工作?【参考方案3】:

由于每个帖子下都会弹出如何设置必填字段的问题,所以我写了一个如何设置必填字段的小例子:

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

【讨论】:

今天有效吗?在这里尝试过,但我在 pojo 上设置的验证不起作用。 是的,它仍然有效。也许这个问题会帮助你***.com/questions/43436804/spring-valid-doesnt-work如果你需要更多帮助,我需要一些示例代码来进一步了解它。【参考方案4】:

我有一个非常相似的问题。实际上问题比我想象的更深。我正在使用 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,向UserPreferences 添加了具有所有可能性的setter/getters 构造函数,但没有机会,因为它将发送的数据识别为5 个参数,但实际上映射的方法只有2 个参数。我也尝试了 Biju 的解决方案,但结果是,spring 使用默认构造函数创建了一个 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 方法中手动进行了转换。在我看来,spring 无法识别发送数据的原因是 content-type。

【讨论】:

我也遇到了同样的问题。我使用@RequestMapping(method = POST, path = "/settings/projectId") public void settings(@PathVariable String projectId, @RequestBody ProjectSettings settings) 做了更简洁的解决方法 @Petr Újezdský 您的解决方案不可行,因为它完全改变了 API(使用请求正文而不是请求参数)【参考方案5】:

虽然引用 @ModelAttribute@RequestParam@PathParam 等的答案是有效的,但我遇到了一个小问题。生成的方法参数是 Spring 包装在 DTO 周围的代理。因此,如果您尝试在需要您自己的自定义类型的上下文中使用它,您可能会得到一些意想不到的结果。

以下将不起作用:

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

在我的例子中,尝试在 Jackson 绑定中使用它会导致 com.fasterxml.jackson.databind.exc.InvalidDefinitionException

您需要从 dto 创建一个新对象。

【讨论】:

【参考方案6】:

是的,您可以通过简单的方式做到这一点。请参阅下面的代码行。

网址 - 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);
     

【讨论】:

【参考方案7】:

接受的答案就像一个魅力,但如果对象有一个对象列表,它将无法按预期工作,所以这是我经过一番挖掘后的解决方案。

按照this thread 的建议,这就是我的做法。

前端:将您的对象字符串化,然后将其编码为 base64 以进行提交。 后端:解码 base64 字符串,然后将字符串 json 转换为所需的对象。

使用 postman 调试 API 并不是最好的方法,但它按我的预期工作。

原始对象: page: 1, size: 5, filters: [ field: "id", value: 1, comparison: "EQ"

编码对象: 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)

这里是 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
)

【讨论】:

以上是关于Spring MVC:复杂对象作为 GET @RequestParam的主要内容,如果未能解决你的问题,请参考以下文章

ASP.Net MVC 模型使用 GET 绑定复杂对象

如何使用 MVC Web Api GET 将对象作为参数传递

如何在 spring-mvc 中为 REST 查询提供对象列表?

Spring MVC 表单验证不适用于嵌套的复杂类型

在从 Spring MVC 作为 JSON 发送时动态忽略 Java 对象中的字段

SpringBoot工程中Spring MVC模块的应用