Spring Boot之实现JSR-303请求参数校验

2024-02-11 18:32

本文主要是介绍Spring Boot之实现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依赖中,并不需要额外引入,所以您在本文中找不到这一步。

这篇关于Spring Boot之实现JSR-303请求参数校验的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/700518

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.