Spring Boot中使用JSR-303实现请求参数校验

2023-10-25 14:52

本文主要是介绍Spring Boot中使用JSR-303实现请求参数校验,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

JSR-303是Java中的一个规范,用于实现请求参数校验。它定义了一组注解,可以应用于JavaBean的字段上,用于验证输入参数的合法性。下面是一些常用的JSR-303注解及其介绍:

@NotNull:用于验证字段值不能为null。
@NotEmpty:用于验证字符串字段不能为空。
@NotBlank:用于验证字符串字段不能为空,并且长度必须大于0。
@Min:用于验证数字字段的最小值。
@Max:用于验证数字字段的最大值。
@Size:用于验证字符串、集合或数组字段的长度或大小。
@Pattern:用于验证字符串字段是否匹配指定的正则表达式。
@Email:用于验证字符串字段是否符合Email格式。
@Range:用于验证数字字段的取值范围。
@Valid:用于嵌套验证,可以对对象中的字段进行递归验证。
其他如下图所示:
在这里插入图片描述

通过在JavaBean的字段上添加这些注解,可以在接收请求参数时进行自动校验。如果校验失败,可以通过异常处理机制来处理校验错误。

需要注意的是,JSR-303只提供了基本的验证注解,如果需要更复杂的校验逻辑,可以自定义注解或使用第三方库,如Hibernate Validator等。
Hibernate Validator附加的constraint:
在这里插入图片描述

至于我们为啥使用JSR-303来校验我们的参数,主要是为了解决我们在实际开发过程中,前后端的参数校验导致的问题。比如:

  1. 我们依靠前端框架解决参数校验,但是缺少服务器的参数校验,这种情况常见于需要同时进行开发前后端的时候,虽然程序的正常使用不会有问题,到那时开发者会忽略非正常的操作,比如绕过前端程序,直接模拟客户端请求,这样就绕过了我们对前端预设的各种限制,直击我们后端的数据访问接口,进而使得我们的后端系统存在严重的安全隐患。
  2. 大量的利用if/else语句嵌套实现,使得我们的校验逻辑晦涩难懂,并不利于我们对整个系统的维护。

JSR的定义标准

验证触发时机:

  • JSR-303校验标准定义了两个触发校验的时机:

    • 在对象被持久化之前(例如,保存到数据库之前)。
    • 在对象被修改之后(例如,更新数据库中的记录后)。
  • 校验约束注解:JSR-303标准提供了一组注解,可以用于对Java对象的属性进行校验。这些注解包括但不限于@NotNull@NotEmpty@Min@Max@Size@Pattern@Email 等。开发人员可以根据需求选择适当的注解来定义校验规则。

  • 校验组:JSR-303允许将校验规则分组,以便在特定情况下选择性地执行校验。开发人员可以为每个校验注解指定一个或多个校验组,然后在校验时选择要执行的校验组。

  • 嵌套校验:JSR-303允许对复杂对象进行嵌套校验,即在校验一个对象时,也会对其关联的其他对象进行校验。这样可以确保整个对象图的完整性和合法性。

  • 自定义校验:JSR-303还允许开发人员定义自己的校验注解和校验器,以满足特定的校验需求。通过实现 ConstraintValidator 接口来自定义校验器,并在自定义注解中使用该校验器。

  • 校验结果:JSR-303校验结果以校验异常的形式返回。当校验失败时,会抛出 ConstraintViolationException 异常,其中包含了校验失败的详细信息,例如校验失败的属性、校验失败的值、校验错误消息等。

接下来我们将围绕我们在Spring Boot的实体类中,使用JSR-303校验。值得注意的是,JSR-303校验我们一般都是对Java的实体类对象进行校验,主要检验在我们的实体类对象的属性上。

还是利用我们==>上一篇 <===的相关依赖文件,这篇我就不在重复导入相关依赖文件了。本篇着重文件主要正在User类和UserController,所用的依赖pom.xml文件和application.properties均与上篇一样,就不在重复描述了。

PS: 你可以看到我在依赖中均采用了lombok,但是我却并未使用这个玩意,原因在于,我使用了lombok

@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;public User(Long id, String name, Integer age, String email) {this.id = id;this.name = name;this.age = age;this.email = email;}public User() {}public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", email='" + email + '\'' +'}';}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;User user = (User) o;return Objects.equals(id, user.id) && Objects.equals(name, user.name) && Objects.equals(age, user.age) && Objects.equals(email, user.email);}@Overridepublic int hashCode() {return Objects.hash(id, name, age, email);}
}
@Api(tags = "用户管理")
@RestController
@RequestMapping(value = "/users")     // 通过这里配置使下面的映射都在/users下
public class UserController {// 创建线程安全的Map,模拟users信息的存储static Map<Long, User> users = Collections.synchronizedMap(new HashMap<>());@GetMapping("/")@ApiOperation(value = "获取用户列表")public List<User> getUserList() {List<User> r = new ArrayList<>(users.values());return r;}@PostMapping("/")@ApiOperation(value = "创建用户", notes = "根据User对象创建用户")public String postUser(@Valid @RequestBody User user) {users.put(user.getId(), user);return "success";}@GetMapping("/{id}")@ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息")public User getUser(@PathVariable Long id) {return users.get(id);}@PutMapping("/{id}")@ApiImplicitParam(paramType = "path", dataType = "Long", name = "id", value = "用户编号", required = true, example = "1")@ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")public String putUser(@PathVariable Long id, @RequestBody User user) {User u = users.get(id);u.setName(user.getName());u.setAge(user.getAge());users.put(id, u);return "success";}@DeleteMapping("/{id}")@ApiOperation(value = "删除用户", notes = "根据url的id来指定删除对象")public String deleteUser(@PathVariable Long id) {users.remove(id);return "success";}
}

接下来启动项目,当然启动类还是需要加:
@EnableSwagger2Doc这个注解

之后,我们可以通过使用相关工具比如Postman等测试工具发起,也可以使用curl发起,比如:
在这里插入图片描述

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/"
}

其中返回名称的各参数含义如下:

timestamp:请求时间
status:HTTP返回的状态码,这里返回400,即:请求无效、错误的请求,通常参数校验不通过均为400
error:HTTP返回的错误描述,这里对应的就是400状态的错误描述:Bad Request
errors:具体错误原因,是一个数组类型;因为错误校验可能存在多个字段的错误,比如这里因为定义了两个参数不能为Null,所以存在两条错误记录信息
message:概要错误消息,返回内容中很容易可以知道,这里的错误原因是对user对象的校验失败,其中错误数量为2,而具体的错误信息就定义在上面的errors数组中
path:请求路径

从errors数组中各个错误明细,知道各个字段的defaultMessage,可以看到很清晰的错误描述。

浏览器访问:
http://localhost:8080/swagger-ui.html

在这里插入图片描述
在这里插入图片描述
我们可以看到校验的部分限制。

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



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

相关文章

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

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

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

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

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

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

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

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

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

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

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

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

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执行过程