请求参数的校验是很多新手开发非常容易犯错,或存在较多改进点的常见场景。比较常见的问题主要表现在以下几个方面:
仅依靠前端框架解决参数校验,缺失服务端的校验。这种情况常见于需要同时开发前后端的时候,虽然程序的正常使用不会有问题,但是开发者忽略了非正常操作。比如绕过前端程序,直接模拟客户端请求,这时候就会突然在前端预设的各种限制,直击各种数据访问接口,使得我们的系统存在安全隐患。
大量地使用if/else语句嵌套实现,校验逻辑晦涩难通,不利于长期维护。
所以,针对上面的问题,建议服务端开发在实现接口的时候,对于请求参数必须要有服务端校验以保障数据安全与稳定的系统运行。同时,对于参数的校验实现需要足够优雅,要满足逻辑易读、易维护的基本特点。
接下来,我们就在本篇教程中详细说说,如何优雅地实现Spring Boot服务端的请求参数校验。
JSR-303在开始动手实践之前,我们先了解一下接下来我们将使用的一项标准规范:JSR-303
什么是JSR?
JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
JSR-303定义的是什么标准?
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
Bean Validation中内置的constraint
Hibernate Validator附加的constraint
在JSR-303的标准之下,我们可以通过上面这些注解,优雅的定义各个请求参数的校验。更多关于JSR的内容可以参与官方文档或参考资料中的引文[1]。
动手实践已经了解了JSR-303之后,接下来我们就来尝试一下,基于此规范如何实现参数的校验!
准备工作读者可以拿任何一个使用Spring Boot 2.x构建的提供RESTful API的项目作为基础。也可以使用Spring Boot 2.x基础教程:使用Swagger2构建强大的API文档中构建的实验工程作为基础,您可以通过下面仓库中的chapter2-2目录取得:
Github:https://github.com/dyc87112/SpringBoot-Learning/tree/2.x
Gitee:https://gitee.com/didispace/SpringBoot-Learning/tree/2.x
当然,您也可以根据前文再构建一个作为复习,也是完全没有问题的。
快速入门我们先来做一个简单的例子,比如:定义字段不能为Null。只需要两步
第一步:在要校验的字段上添加上@NotNull注解,具体如下:
@Data @ApiModel(description="用户实体") public class User { @ApiModelProperty("用户编号") private Long id; @NotNull @ApiModelProperty("用户姓名") private String name; @NotNull @ApiModelProperty("用户年龄") private Integer age; }第二步:在需要校验的参数实体前添加@Valid注解,具体如下:
@PostMapping("http://www.likecs.com/") @ApiOperation(value = "创建用户", notes = "根据User对象创建用户") public String postUser(@Valid @RequestBody User user) { users.put(user.getId(), user); return "success"; }完成上面配置之后,启动应用,并用POST请求访问localhost:8080/users/接口,body使用一个空对象,{}。你可以用Postman等测试工具发起,也可以使用curl发起,比如这样:
curl -X POST \ :8080/users/ \ -H 'Content-Type: application/json' \ -H 'Postman-Token: 72745d04-caa5-44a1-be84-ba9c115f4dfb' \ -H 'cache-control: no-cache' \ -d '{ }'不出意外,你可以得到如下结果:
{ "timestamp": "2019-10-05T05:45:19.221+0000", "status": 400, "error": "Bad Request", "errors": [ { "codes": [ "NotNull.user.age", "NotNull.age", "NotNull.java.lang.Integer", "NotNull" ], "arguments": [ { "codes": [ "user.age", "age" ], "arguments": null, "defaultMessage": "age", "code": "age" } ], "defaultMessage": "不能为null", "objectName": "user", "field": "age", "rejectedValue": null, "bindingFailure": false, "code": "NotNull" }, { "codes": [ "NotNull.user.name", "NotNull.name", "NotNull.java.lang.String", "NotNull" ], "arguments": [ { "codes": [ "user.name", "name" ], "arguments": null, "defaultMessage": "name", "code": "name" } ], "defaultMessage": "不能为null", "objectName": "user", "field": "name", "rejectedValue": null, "bindingFailure": false, "code": "NotNull" } ], "message": "Validation failed for object='user'. Error count: 2", "path": "/users/" }其中返回内容的各参数含义如下:
timestamp:请求时间
status:HTTP返回的状态码,这里返回400,即:请求无效、错误的请求,通常参数校验不通过均为400
error:HTTP返回的错误描述,这里对应的就是400状态的错误描述:Bad Request