前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【译】如何在 Spring 中将 @RequestParam 绑定到对象

【译】如何在 Spring 中将 @RequestParam 绑定到对象

作者头像
AlphaHinex
发布2024-04-30 15:54:35
4670
发布2024-04-30 15:54:35
举报
文章被收录于专栏:周拱壹卒
  • 原文地址:How to bind @RequestParam to object in Spring[1]
  • 原文作者:Daniel Olszewski[2]

你的请求映射方法中是否有多个用 @RequestParam 注解的参数?是否感觉它们影响了易读性?

当请求只有一两个入参时,这个注解看起来非常直观,但是当参数列表变长时,你可能会感到不知所措。

你不能在对象内部使用 @RequestParam 注解,但这并不意味着你没有其他解决方案。在这篇文章中,我将向你展示 如何在 Spring 应用中将多个请求参数绑定到一个对象

过长的 @RequestParams 列表

无论是 controller 还是其他类,我相信你会同意 过长的方法参数列表很难阅读。此外,如果参数类型相同,则更容易出错。

静态代码分析工具,如 Checkstyle 可以检测方法中的大量输入[3],因为这通常被认为是一种不良的实践。

译注:此处原文链接 http://checkstyle.sourceforge.net/config_sizes.html#ParameterNumber 已失效,已更换。

你将一组参数一起传递给应用程序的不同层次是非常常见的。这组参数通常可以 形成一个对象,你需要做的就是 提取它并给它一个适当的名字

让我们来看一个用于搜索产品的 GET 端点的示例:

代码语言:javascript
复制
@RestController
@RequestMapping("/products")
class ProductController {
 
    //...
 
    @GetMapping
    List<Product> searchProducts(@RequestParam String query,
            @RequestParam(required = false, defaultValue = "0") int offset,
            @RequestParam(required = false, defaultValue = "10") int limit) {
        return productRepository.search(query, offset, limit);
    }
 
}

三个参数的数量并不令人担忧,但它们很容易增长。例如,搜索通常包括排序或一些额外的过滤器。在这种情况下,它们都被传递到数据访问层,所以它们似乎是 参数对象[4] 提取的完美候选者。

将 @RequestParam 绑定到 POJO

根据我的经验,开发者不会替换 @RequestParams 的长列表,因为他们单纯的没有意识到这是可能的。@RequestParam 的文档并未提及替代方案。

首先,更新控制器的方法,使其接受 POJO 作为输入,而不是参数列表。

代码语言:javascript
复制
@GetMapping
List<Product> searchProducts(ProductCriteria productCriteria) {
    return productRepository.search(productCriteria);
}

这个 POJO 不需要任何额外的注解。它应该有一系列的字段,这些字段与将从 HTTP 请求绑定的请求参数相匹配,有标准的 getter/setter 方法,以及一个无参数的构造函数。

代码语言:javascript
复制
class ProductCriteria {
 
    private String query;
    private int offset;
    private int limit;
 
    ProductCriteria() {
    }
 
    public String getQuery() {
        return query;
    }
 
    public void setQuery(String query) {
        this.query = query;
    }
 
    // other getters/setters
 
}

在 POJO 内验证请求参数

好的,但我们不仅仅是使用 @RequestParam 注解来绑定 HTTP 参数。该注解的另一个有用特性是可以将给定参数标记为必填项。如果请求中缺少必填参数,我们的端点可以拒绝它。

要在使用 POJO 时达到相同的效果(甚至更多!)我们可以 使用 bean 验证。Java 有许多内置的约束,但你总是可以在需要时 创建自定义验证[5]

Let’s return to our POJO and add some validation rules to fields. If you just want to mimic the behavior of @RequestParam(required = false), all you need is the @NotNull annotation on a required field.

让我们回到我们的 POJO,并向字段添加一些验证规则。如果你只是想 模仿 @RequestParam(required = true) 的行为,你需要的只是 在必填字段上加上 @NotNull 注解

译注:此处原文为 **mimic the behavior of @RequestParam(required = false)**,但是实际上应该是 @RequestParam(required = true),因为 @NotNull 是必填项的意思。

在许多情况下,使用 @NotBlack 替代 @NotNull 更有意义,因为它也覆盖了不希望出现的空字符串问题(长度为零的字符串)。

代码语言:javascript
复制
final class ProductCriteria {
 
    @NotBlank
    private String query;
    @Min(0)
    private int offset;
    @Min(1)
    private int limi;
 
    // ...
 
}

需要注意的是:

仅在字段上添加验证注解还不足以使校验生效。

你还需要在控制器方法的 POJO 参数上标记 @Valid 注解。这样,你就告诉 Spring 在绑定步骤时应执行验证。

代码语言:javascript
复制
@GetMapping
List<Product> searchProducts(@Valid ProductCriteria productCriteria) {
    // ...
}

在 POJO 内设置请求参数的默认值

@RequestParam 注解的另一个有用特性是,当 HTTP 请求中没有参数时,可以定义默认值。

当我们有一个 POJO 时,不需要任何特殊的魔法。你只需要直接为字段指定默认值。当请求中缺少参数时,不会有任何东西覆盖预定义的值。

代码语言:javascript
复制
private int offset = 0;
private int limit = 10;

多个对象

你并不需要将所有的 HTTP 参数放在一个对象中。你可以将参数分组在几个 POJO 中。

为了说明这一点,让我们向我们的端点添加排序条件。首先,我们需要一个单独的对象。就像之前一样,它有一些验证约束。

代码语言:javascript
复制
final class SortCriteria {
 
    @NotNull
    private SortOrder order;
    @NotBlank
    private String attribute;
 
    // constructor, getters/setters
 
}

在 controller 中,你只需将其作为一个单独的输入参数添加。请注意,每个需要验证的参数上都需要 @Valid 注解。

代码语言:javascript
复制
@GetMapping
List<Product> searchProducts(@Valid ProductCriteria productCriteria, @Valid SortCriteria sortCriteria) {
    // ...
}

嵌套对象

作为多个输入请求对象的替代方案,我们也可以使用组合。参数绑定也适用于嵌套对象。

下面你可以找到一个例子,将之前引入的排序条件移动到了产品查询条件 POJO 中。

要校验所有嵌套属性,你应该在嵌套对象字段上添加 @Valid 注解。请注意,当嵌套对象字段为 null 时,Spring 不会验证其属性。如果所有嵌套属性都是可选的,那么这可能是预期的解决方案。如果不是,则需在嵌套对象字段上放置 @NotNull 注解。

代码语言:javascript
复制
final class ProductCriteria {
 
    @NotNull
    @Valid
    private SortCriteria sort;
 
    // ...
 
}

HTTP 参数必须使用点符号与字段名匹配。在我们的例子中,它们应该如下所示:

代码语言:javascript
复制
sort.order=ASC&sort.attribute=name

不可变的 DTO

如今,你可以观察到一种趋势,即从传统的带有 setter 的 POJO 转向不可变对象。

不可变对象有许多好处(也有一些缺点……但嘘)。在我看来,最大的好处是 更易于维护

你是否曾经跟踪你的应用程序穿过几十层,以理解什么条件导致了对象的特定状态?在哪个地方这个或那个字段发生了变化?为什么它被更新?setter 方法的名称并不能解释任何事情。setter 方法没有任何业务含义。

考虑到 Spring 框架创建时的情况,没有人会对 Spring 强烈依赖 POJO 规范感到惊讶。然而,时代变了,旧时使用的模式已渐渐变成了反模式。

没有简单的方法可以通过参数化构造函数将 HTTP 参数神奇地绑定到 POJO。无参数构造函数是不可避免的。然而,我们可以将该构造函数设为 private(但遗憾的是,嵌套对象中不能这样做)并移除所有的 setter 方法。从 public 的视角来看,对象将变得不可变。

默认情况下,Spring 需要 setter 方法将 HTTP 参数绑定到字段。幸运的是,可以重新配置绑定器并使用直接字段访问(通过反射)。

为了给你的整个应用程序配置全局数据绑定器,你可以创建一个 controller advice 组件。你可以在一个带有 @InitBinder 注解的方法中更改绑定器配置,该方法接受绑定器作为输入。

代码语言:javascript
复制
@ControllerAdvice
class BindingControllerAdvice {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.initDirectFieldAccess();
    }

}

创建了这个简洁的类之后,我们可以回到我们的 POJO,并从类中移除所有的 setter 方法,使其对公共使用只读。

代码语言:javascript
复制
final class ProductCriteria {
 
    @NotBlank
    private String query;
    @Min(0)
    private int offset = 0;
    @Min(1)
    private int limit = 10;
 
    private ProductCriteria() {
    }
 
    public String getQuery() {
        return query;
    }
 
    public int getOffset() {
        return offset;
    }
 
    public int getLimit() {
        return limit;
    }
 
}

重启你的应用程序并尝试设置 HTTP 请求的参数。它应该像之前一样正常工作。

结论

在这篇文章中,你可以看到在 Spring MVC controller 中使用 @RequestParam 绑定的 HTTP 请求参数可以很容易地被替换为一个参数对象,该对象将一些属性组织在一起,它是一个普通的 POJO,或者也是一个不可变的 DTO。

你可以 在 GitHub 仓库中找到本文中描述的完整代码[6]。我希望呈现的案例是自解释的,但如果有任何疑问,或者你想发表你的看法,我强烈建议你在文章下方留下你的评论。

参考资料

[1]

How to bind @RequestParam to object in Spring: http://dolszewski.com/spring/how-to-bind-requestparam-to-object/

[2]

Daniel Olszewski: http://dolszewski.com/

[3]

Checkstyle 可以检测方法中的大量输入: https://checkstyle.org/checks/sizes/parameternumber.html#ParameterNumber

[4]

参数对象: https://refactoring.com/catalog/introduceParameterObject.html

[5]

创建自定义验证: http://dolszewski.com/spring/custom-validation-annotation-in-spring/

[6]

在 GitHub 仓库中找到本文中描述的完整代码: https://github.com/danielolszewski/blog/tree/master/spring-requestparam-object

Spring 中如何根据请求调用对应方法

【转】Spring 之 Interceptor

Spring Boot 配置文件拾遗

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 周拱壹卒 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 过长的 @RequestParams 列表
  • 将 @RequestParam 绑定到 POJO
    • 在 POJO 内验证请求参数
      • 在 POJO 内设置请求参数的默认值
      • 多个对象
      • 嵌套对象
      • 不可变的 DTO
      • 结论
      相关产品与服务
      腾讯云代码分析
      腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,助力维护团队卓越代码文化。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档