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

时间:2022-11-06 22:53:18

您是否在请求映射方法中@RequestParam注释了多个参数,并且觉得它不可读?

当请求中预计有一个或两个输入参数时,注释看起来非常简单,但是当列表变长时,您可能会感到不知所措。

您不能在对象中使用@RequestParam注释,但这并不意味着您没有其他解决方案。在这篇文章中,我将向您展示如何用一个对象替换多个@RequestParams

1. @RequestParams列表太长

无论是控制器还是其他类,我相信您都同意一长串方法参数很难阅读。此外,如果参数类型相同,则更容易出错。

像 Checkstyle 这样的静态代码分析工具可以检测方法中的大量输入,因为它被广泛认为是一种不好的做法。

将一组参数一起传递到应用程序的不同层是很常见的。这样的组通常可以形成一个对象,您所要做的就是提取它并给它一个正确的名称

让我们看一下用于搜索某些产品的示例 GET 端点:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@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);
   }
 
}

三个参数不是一个令人担忧的数字,但它很容易增长。例如,搜索通常包括排序顺序或一些其他筛选器。在这种情况下,它们都传递到数据访问层,因此它们似乎是参数对象提取的完美候选项。

2. 将@RequestParam绑定到POJO

根据我的经验,开发人员不会替换长列表@RequestParams因为他们根本不知道这是可能的。@RequestParam的文档没有提到替代解决方案。

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

1
2
3
4
@GetMapping
List<Product> searchProducts(ProductCriteria productCriteria) {
   return productRepository.search(productCriteria);
}

POJO 不需要任何其他注释。它应该有一个与将从 HTTP 请求绑定的请求参数、标准 getter/setter 和无参数构造函数匹配的字段列表。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
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
 
}

2.1. 验证 POJO 中的请求参数

好的,但我们不使用@RequestParam注释来绑定HTTP参数。注释的另一个有用功能是可以根据需要标记给定参数。如果请求中缺少参数,我们的终端节点可以拒绝它。

为了使用 POJO 达到相同的效果(甚至更多!),我们可以使用bean 验证。Java 带有许多内置约束,但如果需要,您始终可以创建自定义验证

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

在许多情况下,使用它更有意义@NotBlack相反@NotNull因为它还涵盖了不需要的空字符串问题(长度为零的字符串)。

01
02
03
04
05
06
07
08
09
10
11
12
final class ProductCriteria {
 
   @NotBlank
   private String query;
   @Min(0)
   private int offset;
   @Min(1)
   private int limi;
 
   // ...
 
}

提醒一句:

添加字段的验证注释不足以使其正常工作。

您还需要在控制器的方法中使用@Valid注释标记 POJO 参数。通过这种方式,您可以通知 Spring 它应该在绑定步骤上执行验证。

1
2
3
4
@GetMapping
List<Product> searchProducts(@Valid ProductCriteria productCriteria) {
   // ...
}

2.2. POJO 中的默认请求参数值

@RequestParam注释的另一个有用功能是能够在参数不存在时定义默认值,而不是HTTP请求。

当我们有一个POJO时,不需要特殊的魔法。您只需将默认值直接分配给字段。当请求中缺少参数时,任何内容都不会覆盖预定义的值。

1
2
private int offset = 0;
private int limit = 10;

3. 多个对象

您不会*将所有 HTTP 参数放在单个对象中。您可以在多个 POJO 中对参数进行分组。

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

01
02
03
04
05
06
07
08
09
10
final class SortCriteria {
 
   @NotNull
   private SortOrder order;
   @NotBlank
   private String sortAttribute;
 
   // constructor, getters/setters
 
}

在控制器中,只需将其添加为单独的输入参数即可。请注意,每个应验证的参数都需要@Valid注释。

1
2
3
4
@GetMapping
List<Product> searchProducts(@Valid ProductCriteria productCriteria, @Valid SortCriteria sortCriteria) {
   // ...
}

4. 嵌套对象

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

您可以在下面找到一个示例,其中先前引入的排序标准已移至产品标准 POJO。

若要验证所有嵌套属性,应将@Valid批注添加到字段中。请注意,如果字段为空,Spring 将不会验证其属性。如果所有嵌套属性都是可选的,则这可能是所需的解决方案。如果没有,只需将@NotNull注释放在该嵌套对象字段上即可。

1
2
3
4
5
6
7
8
9
final class ProductCriteria {
 
   @NotNull
   @Valid
   private SortCriteria sort;
 
   // ...
 
}

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

1
sort.order=ASC&sort.attribute=name

5. 不可变的 DTO

如今,您可以观察到一种趋势,即放弃传统的 POJO 和 setter,转而使用不可变的对象。

不可变对象有几个好处(也有缺点......但是嘘)。在我看来,最大的一个是更简单的维护

您是否曾经跟踪应用程序的数十层,以了解哪些条件会导致对象的特定状态?这个或那个字段在哪个地方发生了变化?为什么要更新?二传手方法的名称并不能解释任何事情。二传手没有意义。

考虑到Spring框架创建的事实,没有人应该对Spring强烈依赖POJO规范感到惊讶。然而,时代变了,旧的模式变成了反模式。

没有简单的方法可以使用参数化构造函数神奇地将 HTTP 参数绑定到 POJO。非参数构造函数是不可避免的。但是,我们可以将该构造函数设为私有(但遗憾的是不在嵌套对象中)并删除所有 setter。从公共角度来看,对象将变得不可变。

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

为了为整个应用程序全局配置数据绑定程序,可以创建控制器建议组件。您可以在使用@InitBinder注释注释的方法中更改绑定程序配置,该方法接受绑定程序作为输入。

1
2
3
4
5
6
7
8
9
@ControllerAdvice
class BindingControllerAdvice {
 
   @InitBinder
   public void initBinder(WebDataBinder binder) {
       binder.initDirectFieldAccess();
   }
 
}

创建该小类后,我们可以返回到 POJO 并从类中删除所有 setter 方法,使其对公共使用是只读的。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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控制器中的HTTP请求参数@RequestParam可以很容易地替换为参数对象,该参数对象对多个属性进行分组,只不过是一个简单的POJO或可选的不可变DTO。

您可以在GitHub 存储库中找到描述的示例。我希望所介绍的案例是不言自明的,但如果有任何疑问,或者您想投入两分钱,我强烈建议您在帖子下方留下您的评论。