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,即:请求无效、错误的请求,通常参数校验不通过均为400
  • error:HTTP返回的错误描述,这里对应的就是400状态的错误描述:Bad Request
  • errors:具体错误原因,是一个数组类型;因为错误校验可能存在多个字段的错误,比如这里因为定义了两个参数不能为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;

}

发起一个可以出发nameageemail都校验不通过的请求,比如下面这样:

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不是,我们可以看到如下图所示的内容:

其中:nameage字段相比上一篇教程中的文档描述,多了一些关于校验相关的说明;而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/
  • Gitee:https://gitee.com/didispace/SpringBoot-Learning/

如果您觉得本文不错,欢迎Star支持,您的关注是我坚持的动力!
更多本系列免费教程连载「点击进入汇总目录」

参考资料

  • [1] https://www.jianshu.com/p/554533f88370
  • [2] http://blog.didispace.com/spring-boot-starter-swagger-1.3.0/

money.jpg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/477372.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

剑指Offer - 面试题17. 打印从1到最大的n位数

1. 题目 输入数字 n&#xff0c;按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3&#xff0c;则打印出 1、2、3 一直到最大的 3 位数 999。 示例 1: 输入: n 1 输出: [1,2,3,4,5,6,7,8,9]说明&#xff1a; 用返回一个整数列表来代替打印 n 为正整数来源&#xff1a;力扣…

“AI不会凉!” 她要扛起中国AI落地的大旗

文 | 卖萌酱"这不是无脑吹&#xff0c;也不是鸡汤。她让中国AI落地变得切实有解了。"AI泡沫破了吗&#xff1f;AI能落地了吗&#xff1f;AI不是PPT问题吗&#xff1f;AI终究只是梦一场&#xff1f;面对行外的人&#xff0c;每当卖萌酱说起自己是做AI的&#xff0c;都…

Spring Boot 2.x基础教程:使用Swagger2构建强大的API文档

随着前后端分离架构和微服务架构的流行&#xff0c;我们使用Spring Boot来构建RESTful API项目的场景越来越多。通常我们的一个RESTful API就有可能要服务于多个不同的开发人员或开发团队&#xff1a;IOS开发、Android开发、Web开发甚至其他的后端服务等。为了减少与其他团队平…

剑指Offer - 面试题7. 重建二叉树(递归)

1. 题目 输入某二叉树的前序遍历和中序遍历的结果&#xff0c;请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 例如&#xff0c;给出前序遍历 preorder [3,9,20,15,7] 中序遍历 inorder [9,3,15,20,7] 返回如下的二叉树&#xff1a;3/ \9 20/ …

郑州大学一女生私自外出染新冠被立案,校方回应:开除!

近日&#xff0c;郑大一女生私自外出会男友&#xff0c;室友替做核酸&#xff0c;感染新冠被立案一事引发大众关注。5月19日&#xff0c;郑州大学一工作人员回应此事称&#xff0c;该私自外出女生已经被自动开除&#xff0c;对室友的处罚要等警方进一步的调查结果&#xff0c;“…

Spring Boot 2.x基础教程:构建RESTful API与单元测试

首先&#xff0c;回顾并详细说明一下在快速入门中使用的Controller、RestController、RequestMapping注解。如果您对Spring MVC不熟悉并且还没有尝试过快速入门案例&#xff0c;建议先看一下快速入门的内容。 Controller&#xff1a;修饰class&#xff0c;用来创建处理http请…

剑指Offer - 面试题11. 旋转数组的最小数字(二分查找)

1. 题目 把一个数组最开始的若干个元素搬到数组的末尾&#xff0c;我们称之为数组的旋转。输入一个递增排序的数组的一个旋转&#xff0c;输出旋转数组的最小元素。例如&#xff0c;数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转&#xff0c;该数组的最小值为1。 示例 1&#…

核酸检测机器人来了!体验者:捅的很温柔

编 | 袁榭、桃子源 | 新智元5月21日&#xff0c;上海人工智能研究院宣布「SAIRI赛瑞」智能核酸采样车2.0问世&#xff0c;仅需30秒便可完成一次采样。在新冠流行持续不断、核酸检测逐渐广域常态化的当下&#xff0c;不止一家机构在开发核酸检测的机器人系统2022年5月21日&#…

AI作画新高度!谷歌发布imagen,效果惊艳全场

文 | 梦晨 鱼羊 发自 凹非寺源 | 量子位在让AI搞创作这件事上&#xff0c;谷歌和OpenAI正面刚起来了。这不&#xff0c;震惊全网的DALLE 2才新鲜出炉一个月&#xff0c;谷歌就派出名为Imagen的选手来打擂台。直接上图对比&#xff0c;左边是谷歌Imagen选手眼中的“猫猫绊倒人类…

Spring Boot 2.x基础教程:配置文件详解

在快速入门一节中&#xff0c;我们轻松的实现了一个简单的RESTful API应用&#xff0c;体验了一下Spring Boot给我们带来的诸多优点&#xff0c;我们用非常少的代码量就成功的实现了一个Web应用&#xff0c;这是传统的Spring应用无法办到的&#xff0c;虽然我们在实现Controlle…

剑指Offer - 面试题12. 矩阵中的路径(DFS回溯)

1. 题目 请设计一个函数&#xff0c;用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始&#xff0c;每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格&#xff0c;那么该路径不能再次进入该格子。例如…

一篇论文8778个作者:人均写5个字,署名用了17页

文 | 梦晨(发自凹非寺)源 | 公众号QbitAI一篇论文有8778位作者是什么感觉&#xff1f;在PDF里&#xff0c;光作者列表就要占17页。有人试图把所有名字排版在同一页&#xff0c;字已经小到看不清。这篇论文来自欧洲核子研究中心的ATLAS对撞机合作组&#xff0c;刷新了他们在2015…

Spring Boot 2.x基础教程:工程结构推荐

Spring Boot框架本身并没有对工程结构有特别的要求&#xff0c;但是按照最佳实践的工程结构可以帮助我们减少可能会遇见的坑&#xff0c;尤其是Spring包扫描机制的存在&#xff0c;如果您使用最佳实践的工程结构&#xff0c;可以免去不少特殊的配置工作。 典型示例 以下结构是…

剑指Offer - 面试题18. 删除链表的节点

1. 题目 给定单向链表的头指针和一个要删除的节点的值&#xff0c;定义一个函数删除该节点。 返回删除后的链表的头节点。 注意&#xff1a;此题对比原题有改动 示例 1: 输入: head [4,5,1,9], val 5 输出: [4,1,9] 解释: 给定你链表中值为 5 的第二个节点&#xff0c;那么…

因为工资补贴邮件被划空银行卡?搜狐全体员工被诈骗始末

文 | 小戏“叮~”如果你的手机弹出了新邮件提示&#xff0c;弹窗显示【xx公司财务部】关于发布最新工资补贴通知……&#xff0c;你会怎么想&#xff1f;当我看到了正确的公司名&#xff0c;与或许有一定道理的工资补贴&#xff0c;这些信息还是会驱使我带着疑惑打开邮件&#…

剑指Offer - 面试题24. 反转链表(遍历/递归)

1. 题目 定义一个函数&#xff0c;输入一个链表的头节点&#xff0c;反转该链表并输出反转后链表的头节点。 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL限制&#xff1a; 0 < 节点个数 < 5000来源&#xff1a;力扣&…

我是粉红猪佩奇,我要把粉色吹风机写进 IJCAI 论文!

文 | 智商掉了一地当小猪佩奇的忠实观众进行AI研究&#xff0c;论文配图居然是这样的风格……在介绍接下来的内容前&#xff0c;我们先来看一则笑话&#xff1a;小猪佩奇哭着对妈妈说&#xff1a;“小伙伴们都说我长得像粉色吹风机。” 猪妈妈安慰道&#xff1a;“尽瞎说&#…

Spring Boot 2.x基础教程:快速入门

简介 在您第1次接触和学习Spring框架的时候&#xff0c;是否因为其繁杂的配置而退却了&#xff1f;在你第n次使用Spring框架的时候&#xff0c;是否觉得一堆反复黏贴的配置有一些厌烦&#xff1f;那么您就不妨来试试使用Spring Boot来让你更易上手&#xff0c;更简单快捷地构建…

剑指Offer - 面试题64. 求1+2+…+n(递归)

1. 题目 求 12…n &#xff0c;要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句&#xff08;A?B:C&#xff09;。 示例 1&#xff1a; 输入: n 3 输出: 6示例 2&#xff1a; 输入: n 9 输出: 45限制&#xff1a; 1 < n < 10000来源&…

AI社区炸锅了!一句话哄一哄就能让GPT-3准确率暴涨61%!

文 | 梦晨 明敏 发自 凹非寺源 | 量子位一觉醒来&#xff0c;机器学习社区炸了锅。因为最新研究发现&#xff0c;只要对GPT-3说一句“让我们一步一步地思考”&#xff0c;就能让它正确回答出以前不会的问题。比如下面这个例子&#xff1a;16个球中有一半是高尔夫球&#xff0c;…