Spring的@RequestParam对象绑定

翻译:叩丁狼教育吴嘉俊

在Spring中,如果在方法参数列表中使用@RequestParam标注多个参数,会让映射方法的可读性大大降低。

如果映射请求的参数只有一两个的话,使用@RequestParam会非常直观,但是如果参数列表越来越长,就很容易晕菜。

虽然我们不能直接在参数对象中使用@RequestParam标签,但是并不代表没有其他的办法。这篇文章就会演示怎么使用对象的封装来简化多个@RequestParams标签。

【注:SpringMVC注入请求参数到对象中,这个对于很多开发是再正常不过的,但是这里强调的是使用@RequestParam来绑定参数,因为@RequestParam可以对绑定参数有更多的限制】

过长的@RequestParam列表

不管是controller还是其他的类,过长的参数列表会让代码的可读性变差,这一点是所有开发人员都认同的。更不要说,如果大量的参数的类型还是一致的情况下,参数就更容易混淆了。

很多代码检查工具,都会把方法的参数个数作为检查条件,也是因为过长的参数列表被认为是一种错误的代码规范。

常见的一种解决方案,就是把一组参数合并起来,并作为应用的独立的一层。常见的,这组参数可以合并到一个对象中,并给予这个对象一个恰当的名字即可。

我们来看一个GET请求服务端的例子:

@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);
   }

}

虽然该方法只有三个参数,但是参数列表很容易增长的,比如既然代码中是查询商品服务,那么常常需要包含按照一些额外的过滤条件进行排序等操作。在我们的代码中,因为参数是直接传递给数据连接层,所以我们可以直接使用ParameterObject模式来处理【注:ParameterObject就是把参数组装成对象】。

使用@RequestParam绑定POJO

根据我的经验,很多开发没有替换较长的@RequestParams列表,主要还是因为他们不知道有什么替代的方案,因为在Spring的文档中没有提及。

下面,我们就开始来阐述替换的方案。首先我们可以使用一个POJO来包装这些参数。

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

就已经完成了!

这个POJO本身没有要求额外的注解,但是POJO本身必须包含和请求参数完全匹配的字段,标准的setter/getter,和一个无参的构造器:

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注解,不仅仅只是为了绑定请求参数,一个非常重要的功能是,我们可以对绑定的参数请求验证,比如参数是否必要,如果请求中缺少该参数,则我们的服务端可以拒绝该请求。

为了达到相同的功能,我们常常使用的替换方案是使用Java Bean Validation。java有很多内置的实现,我们也可以创建自己的bean验证器。

回到我们的POJO,我们想为我们的POJO中的字段添加验证规则。如果想模仿@RequestParam(required = false)的表现,我们可以使用@NotNull注解在对应的字段上即可。

在更多的情况下,我们一般使用@NotBlack多于@NotNull,因为@NotBlank考虑了空字符串的情况。

final class ProductCriteria {

   @NotBlank
   private String query;
   @Min(0)
   private int offset;
   @Min(1)
   private int limi;

   // ...

}

这里务必注意一点:

如果仅仅只是在对象的字段上添加验证注解是不够的。

一定要在controller的方法参数里诶包中,在POJO对应的参数前加上@Valid注解。该注解会让Spring在绑定参数前执行校验动作。

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

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

@RequestParam注解的另一个非常有用的功能就是设置参数的默认值。

如果我们使用POJO的方式来绑定参数,没有什么特别牛逼的方法,只需要在定义参数的时候设置好字段的默认值就行了。如果请求中没有该参数,Spring不会把参数的默认值覆盖为null的。

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

绑定多个参数对象

一般情况下,我们也不会强行把所有请求参数全部封装到一个对象中,我们可以把请求参数按照功能分布到多个POJO中。

为了验证这点,我们在查询方法中,添加一个排序的功能。首先,我们需要一个额外的对象,并添加一些校验约束:

final class SortCriteria {

   @NotNull
   private SortOrder order;
   @NotBlank
   private String sortAttribute;

   // constructor, getters/setters

}

在controller中,我们只需要把这个POJO作为另一个参数即可。但是仍然注意,想让校验生效,还是需要在参数对象前添加@Valid注解。

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

内嵌对象

另一种处理请求参数对象的方式是使用组合。参数绑定对这种内嵌对象同样适用。

下面我们给出一个改进的例子,把查询对象移动到ProductCriteria中。

要让内置对象的字段能够执行验证,我们需要在内置对象对应的字段上添加@Valid注解。注意一点的是,如果这个内置对象的字段是null,Spring是不会校验这个属性的,这可以简单理解为,所有的内置对象属性都是可选的。如果想避免这种情况,在内置对象的字段上添加@NotNull注解。

final class ProductCriteria {

   @NotNull//注意这个@NotNull注解
   @Valid
   private SortCriteria sort;

   // ...

}

HTTP请求参数必须按照参数路径的方式命名。比如我们的例子中,请求参数就必须是:

sort.order=ASC&sort.attribute=name

不可变的DTO

现在,我们发现一个趋势,越来越多的开发会把传统的POJO中的setter方法去掉,让POJO变成一个不可变对象。

不可变对象有很多的好处,但是在我看来,最大的优势在于便于维护。

在你的开发中,是否有这样的情况,开着debug,在整个应用大量的代码中,去追踪一个对象的状态是怎么变化的?在什么地方,一个状态发生了什么样的变化?什么情况下,这个对象状态需要修改?对于很多对象来说,仅仅从setter方法的名字来看,是很难看出具体的业务逻辑的。

当Spring框架刚被创建出来的时候,是严格按照Java Bean规范来开发的。但是,时至今日,很多过去推崇的模式,已经变成了反模式。

要去掉setter方法,绑定请求参数,有两种方式,通过构造器或者字段直接绑定。但是目前没有一种非常简单的办法,直接通过构造方法将请求参数绑定,因为默认的构造方法是必须的。虽然我们可以把POJO的构造方法变为private的,并移除掉setter方法,从外部访问来看,确实变成了私有的,但是这种方式有一定的缺陷,比如内部组合对象无法使用这种方式。

默认情况下,Spring要求通过字段的setter方法来绑定参数,但是我们可以通过自定义的绑定器(binder)来直接把请求参数通过字段绑定。

为了可以在我们整个应用中,都使用这种绑定方式,我么你可以定义一个controller advice组件。通过@InitBinder 注解,来修改默认的绑定请求参数方法。

@ControllerAdvice
class BindingControllerAdvice {

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

}

当我们创建好这个类之后,我们就可以把POJO中所有的setter方法去掉,让我们的POJO成为不可变对象。

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;
   }

}

小结

在本文中,我们总结了在Spring MVC控制器中使用参数对象来简化过长的@RequestParam参数列表绑定;并讨论了如果使用不可变的DTO对象来替换简单的POJO。

原文:https://www.javacodegeeks.com/2018/10/how-bind-requestparam-object-spring.html

想获取更多技术视频,请前往叩丁狼官网:http://www.wolfcode.cn/openClassWeb_listDetail.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270