Spring Boot 2.x基础教程:JSR-303实现请求参数校验
请求参数的校验是很多新手开发非常容易犯错,或存在较多改进点的常见场景。比较常见的问题主要表现在以下几个方面:
- 仅依靠前端框架解决参数校验,缺失服务端的校验。这种情况常见于需要同时开发前后端的时候,虽然程序的正常使用不会有问题,但是开发者忽略了非正常操作。比如绕过前端程序,直接模拟客户端请求,这时候就会突然在前端预设的各种限制,直击各种数据访问接口,使得我们的系统存在安全隐患。
- 大量地使用
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("/")
@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 \
http://localhost: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,即:请求无效、错误的请求,通常参数校验不通过均为400error
:HTTP返回的错误描述,这里对应的就是400状态的错误描述:Bad Requesterrors
:具体错误原因,是一个数组类型;因为错误校验可能存在多个字段的错误,比如这里因为定义了两个参数不能为Null
,所以存在两条错误记录信息message
:概要错误消息,返回内容中很容易可以知道,这里的错误原因是对user对象的校验失败,其中错误数量为2
,而具体的错误信息就定义在上面的errors
数组中path
:请求路径
请求的调用端在拿到这个规范化的错误信息之后,就可以方便的解析并作出对应的措施以完成自己的业务逻辑了。
尝试一些其他校验
在完成了上面的例子之后,我们还可以增加一些校验规则,比如:校验字符串的长度、校验数字的大小、校验字符串格式是否为邮箱等。下面我们就来定义一些复杂的校验定义,比如:
@Data
@ApiModel(description="用户实体")
public class User {
@ApiModelProperty("用户编号")
private Long id;
@NotNull
@Size(min = 2, max = 5)
@ApiModelProperty("用户姓名")
private String name;
@NotNull
@Max(100)
@Min(10)
@ApiModelProperty("用户年龄")
private Integer age;
@NotNull
@Email
@ApiModelProperty("用户邮箱")
private String email;
}
发起一个可以出发name
、age
、email
都校验不通过的请求,比如下面这样:
curl -X POST \
http://localhost:8080/users/ \
-H 'Content-Type: application/json' \
-H 'Postman-Token: 114db0f0-bdce-4ba5-baf6-01e5104a68a3' \
-H 'cache-control: no-cache' \
-d '{
"name": "abcdefg",
"age": 8,
"email": "aaaa"
}'
我们将得到如下的错误返回:
{
"timestamp": "2019-10-05T06:24:30.518+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Size.user.name",
"Size.name",
"Size.java.lang.String",
"Size"
],
"arguments": [
{
"codes": [
"user.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
},
5,
2
],
"defaultMessage": "个数必须在2和5之间",
"objectName": "user",
"field": "name",
"rejectedValue": "abcdefg",
"bindingFailure": false,
"code": "Size"
},
{
"codes": [
"Min.user.age",
"Min.age",
"Min.java.lang.Integer",
"Min"
],
"arguments": [
{
"codes": [
"user.age",
"age"
],
"arguments": null,
"defaultMessage": "age",
"code": "age"
},
10
],
"defaultMessage": "最小不能小于10",
"objectName": "user",
"field": "age",
"rejectedValue": 8,
"bindingFailure": false,
"code": "Min"
},
{
"codes": [
"Email.user.email",
"Email.email",
"Email.java.lang.String",
"Email"
],
"arguments": [
{
"codes": [
"user.email",
"email"
],
"arguments": null,
"defaultMessage": "email",
"code": "email"
},
[],
{
"defaultMessage": ".*",
"codes": [
".*"
],
"arguments": null
}
],
"defaultMessage": "不是一个合法的电子邮件地址",
"objectName": "user",
"field": "email",
"rejectedValue": "aaaa",
"bindingFailure": false,
"code": "Email"
}
],
"message": "Validation failed for object='user'. Error count: 3",
"path": "/users/"
}
从errors
数组中的各个错误明细中,知道各个字段的defaultMessage
,可以看到很清晰的错误描述。
Swagger文档中的体现
可能有读者会问了,我的接口中是定了这么多。上一篇教程中,不是还教了如何自动生成文档么,那么对于参数的校验逻辑该如何描述呢?
这里要分两种情况,Swagger自身对JSR-303有一定的支持,但是支持的并那么完善,并没有覆盖所有的注解的。
比如,上面我们使用的注解是可以自动生成的,启动上面我们的实验工程,然后访问http://localhost:8080/swagger-ui.html
,在Models
不是,我们可以看到如下图所示的内容:
其中:name
和age
字段相比上一篇教程中的文档描述,多了一些关于校验相关的说明;而email
字段则没有体现相关校验说明。目前,Swagger共支持以下几个注解:@NotNull
、@Max
、@Min
、@Size
、@Pattern
。在实际开发过程中,我们需要分情况来处理,对于Swagger支自动生成的可以利用原生支持来产生,如果有部分字段无法产生,则可以在@ApiModelProperty
注解的描述中他,添加相应的校验说明,以便于使用方查看。
番外:也许你会有这些疑问
当请求参数校验出现错误信息的时候,错误格式可以修改吗?
答案是肯定的。这里的错误信息实际上由Spring Boot的异常处理机制统一组织并返回的,我们将在后面的教程中详细介绍,Spring Boot是如何统一处理异常返回以及我们该如何定时异常返回。
spring-boot-starter-validation
是必须的吗?
有读者之前问过,看到很多教程都写了还要引入spring-boot-starter-validation
依赖,这个依赖到底是否需要?(本篇中并没有引入)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
其实,只需要仔细看一下spring-boot-starter-validation
依赖主要是为了引入了什么,再根据当前自己使用的Spring Boot版本来判断即可。实际上,spring-boot-starter-validation
依赖主要是为了引入下面这个依赖:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.14.Final</version>
<scope>compile</scope>
</dependency>
我们可以看看当前工程的依赖中是否有它,就可以判断是否还需要额外引入。在Spring Boot 2.1版本中,该依然其实已经包含在了spring-boot-starter-web
依赖中,并不需要额外引入,所以您在本文中找不到这一步。
代码示例
本文的完整工程可以查看下面仓库中的chapter2-3
目录:
- Github:https://github.com/dyc87112/SpringBoot-Learning/tree/2.x
- Gitee:https://gitee.com/didispace/SpringBoot-Learning/tree/2.x
如果您觉得本文不错,欢迎Star支持,您的关注是我坚持的动力!
参考资料
- [1] https://www.jianshu.com/p/554533f88370
- [2] http://blog.didispace.com/spring-boot-starter-swagger-1.3.0/
欢迎关注我的公众号:程序猿DD,获得独家整理的学习资源和日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客:didispace.com
Spring Boot 2.x基础教程:JSR-303实现请求参数校验的更多相关文章
- Spring Boot 2.x基础教程:使用Swagger2构建强大的API文档
随着前后端分离架构和微服务架构的流行,我们使用Spring Boot来构建RESTful API项目的场景越来越多.通常我们的一个RESTful API就有可能要服务于多个不同的开发人员或开发团队:I ...
- Spring Boot 2.x基础教程:Swagger接口分类与各元素排序问题详解
之前通过Spring Boot 2.x基础教程:使用Swagger2构建强大的API文档一文,我们学习了如何使用Swagger为Spring Boot项目自动生成API文档,有不少用户留言问了关于文档 ...
- Spring Boot 2.x基础教程:Swagger静态文档的生成
前言 通过之前的两篇关于Swagger入门以及具体使用细节的介绍之后,我们已经能够轻松地为Spring MVC的Web项目自动构建出API文档了.如果您还不熟悉这块,可以先阅读: Spring Boo ...
- Spring Boot 2.x基础教程:使用国产数据库连接池Druid
上一节,我们介绍了Spring Boot在JDBC模块中自动化配置使用的默认数据源HikariCP.接下来这一节,我们将介绍另外一个被广泛应用的开源数据源:Druid. Druid是由阿里巴巴数据库事 ...
- Spring Boot 2.x基础教程:找回启动日志中的请求路径列表
如果您看过之前的Spring Boot 1.x教程,或者自己原本就对Spring Boot有一些经验,或者对Spring MVC很熟悉.那么对于Spring构建的Web应用在启动的时候,都会输出当前应 ...
- Spring Boot 2.x基础教程:使用MyBatis的XML配置方式
上一篇我们介绍了如何在Spring Boot中整合我们国人最常用的MyBatis来实现对关系型数据库的访问.但是上一篇中使用了注解方式来实现,而对于很多MyBatis老用户还是习惯于XML的开发方式, ...
- Spring Boot 2.x基础教程:Spring Data JPA的多数据源配置
上一篇我们介绍了在使用JdbcTemplate来做数据访问时候的多数据源配置实现.接下来我们继续学习如何在使用Spring Data JPA的时候,完成多数据源的配置和使用. 添加多数据源的配置 先在 ...
- Spring Boot 2.x基础教程:事务管理入门
什么是事务? 我们在开发企业应用时,通常业务人员的一个操作实际上是对数据库读写的多步操作的结合.由于数据操作在顺序执行的过程中,任何一步操作都有可能发生异常,异常会导致后续操作无法完成,此时由于业务逻 ...
- Spring Boot 2.x基础教程:进程内缓存的使用与Cache注解详解
随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一.Spring 3开始提供了强大的基于注解的缓 ...
随机推荐
- 理解Js的parseInt(转)
parseInt() 方法首先查看位置 0 处的字符,判断它是否是个有效数字:如果不是,该方法将返回 NaN,不再继续执行其他操作.但如果该字符是有效数字,该方法将查看位置 1 处的字符,进行同样的测 ...
- codeforces 389 D. Fox and Minimal path(构造+思维)
题目链接:https://vjudge.net/contest/175446#problem/J 题解:显然要用最多n个点构成的图要使的得到的最短路条数有1e9次个,显然要有几个数相乘容易想到2的几进 ...
- 详解RMQ-ST算法 ST模板
RMQ问题是求解区间最值的问题. 这里分析的是ST算法,它可以对所有要处理的数据做到O(nlogn)的预处理,对每个区间查询做到O(1)查询 ST算法本质是一个DP的过程 这里通过举一个求最大值实例来 ...
- Atcoder C - Nuske vs Phantom Thnook(递推+思维)
题目链接:http://agc015.contest.atcoder.jp/tasks/agc015_c 题意:给一个n*m的格,蓝色的组成路径保证不成环,q个询问,计算指定矩形区域内蓝色连通块的个数 ...
- Redis哨兵模式实现集群的高可用
先了解一下哨兵都 做了什么工作:Redis 的 Sentinel 系统用于管理多个 Redis 服务器(instance), 该系统执行以下三个任务: 监控(Monitoring): Sentinel ...
- 【Offer】[17] 【打印1到最大的n位数】
题目描述 思路分析 测试用例 Java代码 代码链接 题目描述 输入数字n,按顺序打印出从1最大的n位十进制数.比如输入3,则打印出1.2.3一直到最大的3位数即999. 思路分析 要考虑到大数问题, ...
- [币严区块链]数字货币交易所之瑞波(XRP)钱包对接
对接Ripple(XRP),不需要本地部署钱包,直接访问Ripple API,本文包括访问Ripple API及如何免费获取测试的XRP. 对接流程 安装Ripple API Ripple API 接 ...
- MySQL-注释-Navicat基本使用-复杂查询练习题-解题思路-pymysql操作数据库-SQL注入-05
目录 mysql语句注释 navicat 的基本使用 特色(个人总结) 与数据服务器建立连接 创建&打开数据库.表 创建 打开 修改操作表结构 修改表结构 查询修改操作表数据 基本语句对应的操 ...
- android View的测量和绘制
本篇内容来源于android 群英传(徐易生著) 我写到这里,是觉得徐易生讲的确实很好, 另外加入了一些自己的理解,便于自己基础的提高. 另外参考:http://www.gcssloop.com/cu ...
- SQLServer的排序规则(字符集编码)
SQLServer的排序规则(字符集编码) 一.总结 1.SQLServer中的排序规则就是其他关系型数据库里所说的字符集编码: 2.SQLServer中的排序规则可以在3处设置,如下: 服务器级别( ...