您是否在请求映射方法中用@RequestParam注释了多个参数,并认为它不可读?
当请求中需要一个或两个输入参数时,注释看起来非常简单,但是当列表变长时,您可能会感到不知所措。
您不能在对象内部使用@RequestParam批注,但这并不意味着您没有其他解决方案。 在本文中,我将向您展示如何用object替换多个@RequestParams 。
1. @RequestParams的列表太长
无论是控制器还是其他类,我都相信您同意很难读取一长串方法参数 。 另外,如果参数类型相同,则更容易出错。
诸如Checkstyle之类的静态代码分析工具可以检测方法中的大量输入,因为它被广泛认为是一种不好的做法。
通常,将一组参数一起传递到应用程序的不同层。 这样的组通常可以形成一个对象 ,您要做的就是提取它并给它起一个适当的名称 。
让我们看一下用于搜索某些产品的示例GET端点:
@RestController
@RequestMapping("/products")
class ProductController {
//...
@GetMapping
List 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作为输入而不是参数列表。
@GetMapping
List searchProducts(ProductCriteria productCriteria) {
return productRepository.search(productCriteria);
}
POJO不需要任何其他注释。 它应具有与将与HTTP请求绑定的请求参数匹配的字段列表,标准的getter / setter和无参数的构造函数。
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带有许多内置约束,但是如果需要,您总是创建一个自定义验证 。
让我们回到POJO并向字段添加一些验证规则。 如果只想模仿 @RequestParam(required = false) 的行为,则只需在必填字段上使用 @NotNull 批注 。
在许多情况下,使用@NotBlack代替@NotNull更有意义,因为它还涵盖了不需要的空字符串问题(长度为零的字符串)。
final class ProductCriteria {
@NotBlank
private String query;
@Min(0)
private int offset;
@Min(1)
private int limi;
// ...
}
请注意:
添加字段的验证注释不足以使其起作用。
您还需要在控制器的方法中使用@Valid批注标记POJO参数。 这样,您通知Spring它应该在绑定步骤上执行验证。
@GetMapping
List searchProducts(@Valid ProductCriteria productCriteria) {
// ...
}
POJO内部的默认请求参数值
@RequestParam批注的另一个有用的功能是能够在HTTP请求中未提供参数时定义默认值。
当我们拥有POJO时,不需要特殊的魔术。 您只需将默认值直接分配给字段。 当请求中缺少参数时,没有任何内容将覆盖预定义的值。
private int offset = 0;
private int limit = 10;
3.多个对象
您没有被迫将所有HTTP参数放在单个对象中。 您可以在多个POJO中对参数进行分组。
为了说明这一点,让我们向端点添加排序条件。 首先,我们需要一个单独的对象。 就像之前一样,它具有一些验证约束。
final class SortCriteria {
@NotNull
private SortOrder order;
@NotBlank
private String sortAttribute;
// constructor, getters/setters
}
在控制器中,只需将其添加为单独的输入参数即可。 请注意,@ Valid批注在每个应验证的参数上都是必需的。
@GetMapping
List searchProducts(@Valid ProductCriteria productCriteria, @Valid SortCriteria sortCriteria) {
// ...
}
4.嵌套对象
作为多个输入请求对象的替代,我们也可以使用组合。 参数绑定也适用于嵌套对象。
在下面,您可以找到一个示例,其中先前引入的排序条件已移至产品条件POJO。
要验证所有嵌套属性,应将@Valid批注添加到该字段。 请注意,如果该字段为null,Spring将不会验证其属性。 如果所有嵌套属性都是可选的,那可能是理想的解决方案。 如果不是,只需将@NotNull批注放在该嵌套对象字段上。
final class ProductCriteria {
@NotNull
@Valid
private SortCriteria sort;
// ...
}
HTTP参数必须使用点符号匹配字段名称。 在我们的情况下,它们应如下所示:
sort.order=ASC&sort.attribute=name
5.不变的DTO
如今,您可以看到一种趋势,它倾向于使用不固定对象,而使用二传手来取代传统的POJO。
不可变的对象有很多好处(还有缺点……但是……)。 我认为,最大的一项是维护简单 。
您是否曾经在应用程序的数十个层中进行过跟踪,以了解哪些条件导致了对象的特定状态? 这个或那个字段在哪里改变了? 为什么要更新? setter方法的名称什么也没解释。 二传手没有任何意义。
考虑到创建Spring框架的事实,Spring强烈依赖POJO规范就不会让人感到惊讶。 然而,时代变了,旧的模式变成了反模式。
没有简单的方法可以使用参数化的构造函数将HTTP参数神奇地绑定到POJO。 非参数构造函数是不可避免的。 但是,我们可以将该构造函数设为私有 (但遗憾的是不能在嵌套对象中使用)并删除所有的setter。 从公众的角度来看,该对象将变得不可变。
默认情况下,Spring需要使用setter方法将HTTP参数绑定到字段。 幸运的是,可以重新配置绑定程序并使用直接字段访问(通过反射)。
为了为整个应用程序全局配置数据绑定器,您可以创建一个控制器建议组件。 您可以在以@InitBinder批注注释的方法内部更改绑定程序配置,该方法接受绑定程序作为输入。
@ControllerAdvice
class BindingControllerAdvice {
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.initDirectFieldAccess();
}
}
创建该小类后,我们可以返回到POJO并从该类中删除所有setter方法,以使其变为只读状态以供公众使用。
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请求的参数。 它应该像以前一样工作。
结论
在本文中,您可以看到使用@RequestParam绑定在Spring MVC控制器中的HTTP请求参数可以轻松地替换为对多个属性进行分组的参数对象,仅不过是简单的POJO或可选的不可变DTO。
您可以在GitHub存储库中找到描述的样本 。 我希望所介绍的案例是不言自明的,但是如果有任何疑问或您想花两分钱,我强烈建议您将您的评论留在帖子下方。
翻译自: https://www.javacodegeeks.com/2018/10/how-bind-requestparam-object-spring.html