您是否在请求映射方法中@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 存储库中找到描述的示例。我希望所介绍的案例是不言自明的,但如果有任何疑问,或者您想投入两分钱,我强烈建议您在帖子下方留下您的评论。