SpringBoot如何优雅的进行参数校验

一、传统参数校验

虽然往事不堪回首,但还是得回忆一下我们传统参数校验的痛点。

下面是我们传统校验用户名和邮箱是否合法的代码

if (username == null || username.isEmpty()) {throw new IllegalArgumentException("用户名不能为空");
}if (isValidEmail(email)) {throw new IllegalArgumentException("邮箱格式不正确");
}public boolean isValidEmail(String email) {String emailRegex = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";Pattern pattern = Pattern.compile(emailRegex);Matcher matcher = pattern.matcher(email);return matcher.matches();
}

这样的代码不仅冗长,而且难以维护,尤其是在多个地方重复使用时,容易出错。

面对上面的痛点,我们就得解放双手,利用框架来完成校验。

它只需要通过简单的注解来定义校验规则,让框架来帮助我们处理校验逻辑,让我们代码变得更加的优雅。

二、几个名词

问题①:JSR是什么?

JSR(Java Specification Requests) 是一套 JavaBean 参数校验的标准,它定义了很多常用的校验注解。

我们可以直接将这些注解加在我们 JavaBean 的属性上面,这样就可以在需要校验的时候进行校验了,非常方便!

问题②:Bean Validation是什么?

Bean Validation是一个抽象的框架,它定义了验证规则,而不会涉及具体的业务逻辑

问题③:Hibernate Validator是什么?

Bean Validation的实现,目前最新版的 Hibernate Validator 6.x 是 Bean Validation 2.0(JSR 380)的参考实现

三、所需依赖

Spring boot 2.3以前版本,Springbootspring-boot-starter-web默认内置了Hibernate-Validator

这些版本直接引入spring-boot-starter-web即可,后面的版本需要单独引入

在后面的测试中会用到lombokSpringBootwebtest等基础依赖,这里就不一一给出

四、注解及作用

看到这些注解后,大家可能会对【@NotNul@NotEmpty@NotBlank】这三个注解有点不理解,这里稍作解释

  • @NotNull:任何对象的value不能为null。

  • @NotEmpty:集合对象的元素不为0,即集合不为空,也可以用于字符串不为null。

  • @NotBlank:只能用于字符串不为null,并且字符串trim()以后length要大于0。

五、快速入门

5.1 新增加一个一个User 实体类
@Data
public class User {//姓名@NotBlank(message = "用户名不能为空")  //注解确保姓名不为空private  String name;//性别@NotBlank(message = "性别不能为空")   //注解确保性别不为空private String sex;//年龄@NotNull(message = "年龄不能为空")  //注解确保年龄不为空@Max(value = 120,message = "年龄不能大于120")  //注解确保年龄必须小于等于120@Min(value = 18,message = "年龄不能小于18")   //注解确保年龄必须大于等于18private  Integer age;//邮箱@Email(message = "邮箱格式不正确")    //注解确保邮箱格式正确@NotBlank(message = "邮箱不能为空")private String email;
}

上述代码说明:

  • @NotBlank: 此注解确保字符串不为空并且不能为空字符串,且去掉前后空格后的长度必须大于 0。它常用于字符串字段验证。message 属性用于指定提示信息;

  • @NotNull: 此注解确保整数类型不能为 null

  • @Min 和 @Max: 这两个注解用于验证数字值是否在指定的范围内。例如,在上面的示例中,我们想要确保 age 的值在 18 到 120 之间;

  • @Email: 此注解用于验证字符串值是否是有效的电子邮件地址格式。

5.2 Controller层参数校验

下图是controller层校验流程

Controller层校验流程

@RestController
public class ValidatorController {//测试参数校验@RequestMapping("/testValidator")public ResponseEntity<String> testValidator(@Valid @RequestBody User  user, BindingResult bindingResult){// 是否存在校验错误if (bindingResult.hasErrors()) {// 获取校验不通过字段的提示信息String errorMsg = bindingResult.getFieldErrors().stream().map(FieldError::getDefaultMessage).collect(Collectors.joining(", "));return ResponseEntity.badRequest().body(errorMsg);}return ResponseEntity.ok("参数校验成功");}}

解释一下上面代码:

  • @Validated: 告诉 Spring 需要对 User 对象执行校验; 这个一定不要忘记加上

  • BindingResult : 该类包含校验不通过时的异常信息,校验不通过时,我们通过这个对象来获取注解中message="xxx"中的内容

注意:当注解校验不通过时,直接将异常信息返回给前端其实并不友好,我们可以将异常包装一下再丢给前端

5.3 测试校验结果

这里我们使用postman工具测试一下参数校验是否成功

① 入参正确情况

{"name":"小凡","sex":"男","age":18,"email":"xiezhr@qq.com"
}

入参正确

入参不正确的情况

{"name":null,"sex":"","age":17,"email":"xiezhrqq.com"
}

入参不正确

通过上面的入门小案例,你学会了么?

上面的返回结果看起来可能不是那么优雅,那么怎么封装统一返回结果呢,

传送门在此优雅的封装返回结果

六、单个参数校验

上面快速入门中我们说了实体参数校验,这小节,我们来看看单个参数的校验

6.1 controller层校验代码

@RequestMapping("/testSingleParmaValidator")
public ResponseEntity<String> testSingleParmaValidator(@NotBlank(message = "姓名不能为空") String name,@Min(value = 18,message = "年龄不能小于18")@Max(value = 120,message = "年龄不能大于120") Integer age){// 参数校验return ResponseEntity.ok("参数校验成功");
}

6.2 全局异常捕获

当参数校验不通过会发生如下异常信息

异常信息

这里我们不能像上面一样通过BindingResult 来获取异常信息,需要添加全局异常捕获校验失败异常,具体代码如下

@RestControllerAdvice
public class GlobalExceptionHandler {//处理ValidationException异常@ExceptionHandler(ValidationException.class)//返回状态码为400@ResponseStatus(HttpStatus.BAD_REQUEST)public ResponseEntity<String> handleValidationExceptions(ValidationException  ex) {String  message = "";//判断异常类型if(ex instanceof ConstraintViolationException){ConstraintViolationException exs = (ConstraintViolationException) ex;//获取验证不通过的信息Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();//遍历验证不通过的信息for (ConstraintViolation<?> item : violations) {//将验证不通过的信息拼接到message中message+=item.getMessage()+",";}}//返回错误信息return ResponseEntity.badRequest().body(message);}}

6.3 测试校验结果

入参正确情况

http://localhost:8080/testSingleParmaValidator?name=小凡&age=18

入参正确的情况

入参不正确情况

http://localhost:8080/testSingleParmaValidator?name=&age=17

入参不正确情况

八、参数校验分组

在实际开发中,我们会遇到这样的情况:同一个实体类可能会在多个接口中使用,但每次的校验场景又不一样。

例如:新增用户和修改用户接口,参数都是User 实体,在新增用户的时候ID字段 可以为空,但name字段 不能为空

在修改用户的是由ID字段不能为空,这种时候就可以使用参数分组来实现。

8.1 定义验证分组接口

定义两个分组接口CreateUserGroup(用户创建组),UpdateUserGroup(用户更新组),

分别继承javax.validation.groups.Default,标识不同的业务场景

public interface CreateUserGroup extends Default {
}public interface UpdateUserGroup extends Default {
}

注:继承Default并不是必须的。只是说,如果继承了Default,那么@Validated(value = Create.class)的校验范畴就为【Create】和【Default】;如果没继承Default,那么@Validated(value = Create.class)的校验范畴只为【Create】,而@Validated(value = {Create.class, Default.class})的校验范畴才为【Create】和【Default】

8.2 分组校验的使用

① 在实体中添加groups 属性

@Data
public class User {//用户ID@NotNull(message = "用户ID不能为空",groups = UpdateUserGroup.class)  //用户更新接口必须传递用户IDprivate Integer id;//姓名@NotBlank(message = "用户名不能为空",groups = CreateUserGroup.class)  //用户创建接口必须传递用户名private  String name;//性别@NotBlank(message = "性别不能为空")   //注解确保性别不为空private String sex;//年龄@NotNull(message = "年龄不能为空")  //注解确保年龄不为空@Max(value = 120,message = "年龄不能大于120")  //注解确保年龄必须小于等于120@Min(value = 18,message = "年龄不能小于18")   //注解确保年龄必须大于等于18private  Integer age;//邮箱@Email(message = "邮箱格式不正确")    //注解确保邮箱格式正确@NotBlank(message = "邮箱不能为空")private String email;
}

②在接口中使用分组

使用 @Validated 注解,并指定要执行的验证组。

//添加用户
@PostMapping("/addUser")
public ResponseEntity<User> addUser(@Validated(value= CreateUserGroup.class) @RequestBody User user){return ResponseEntity.ok(user);
}
//更新用户
@PutMapping("/updateUser")
public ResponseEntity<User> updateUserUser(@Validated(value= UpdateUserGroup.class) @RequestBody User user){return ResponseEntity.ok(user);
}

我们指定create接口指定CreateUserGroup分组,update接口指定UpdateUserGroup

8.3 测试一下接口

接口入参

{"name":"小凡","sex":"男","age":18,"email":"xiezhr@qq.com"
}

addUser接口添加用户,不需要id,验证通过

添加用户

updateUser接口修改用户,需要传入id,校验不通过

修改用户

九、嵌套对象校验

9.1 构造一个员工信息表
@Data
public class Emp {@NotBlank(message = "员工编号不能为空")private  String empNo;@NotBlank(message = "员工姓名不能为空")private  String empName;@NotBlank(message = "员工职位不能为空")private  String job;@Valid                  //这里必须使用@Valid注解private  Dept dept;
}

@Data
public class Dept {@NotBlank(message = "部门编号不能为空")private String  deptNo;@NotBlank(message = "部门名称不能为空")private String  deptName;
}

在这个示例中, Dept 类包含三个字段需要校验: deptNo 和``deptName字段,通过在Dept类中的每个字段上添加相应的校验注解,然后在Emp类中的dept字段上添加@Valid` 注解,可以实现对嵌套对象中多个字段进行参数校验。

9.2 嵌套对象的使用
@PostMapping("/emp")
public ResponseEntity<String> createOrder(@Valid @RequestBody Emp emp) {return ResponseEntity.ok("参数校验成功");
}

9.3 测试一下

① 正确入参情况

{"empNo":"10001","empName":"小凡","job":"程序员","dept":{"deptNo":"20001","deptName":"研发部111"}
}

正确入参情况

② 不正确入参情况

{"empNo":"10001","empName":"","job":"程序员","dept":{"deptNo":"20001","deptName":""}
}

不正确入参情况

十、自定义参数校验

SpringBoot 提供的注解校验功能可以满足大多数的验证需求,但如果在系统中需要实现一些特殊的校验功能时,

我们可以根据规则自定义校验

下面我们来手把手教你自定义一个字符串校验,校验字符串必须为大写或小写

10.1 自定义注解类

我们要自定义验证功能,需要首先自定义注解,以便我们在实体类中使用它,代码如下

①定义一个枚举类 CaseMode :

public enum CaseMode {UPPER,LOWER;
}

②创建一个自定义的校验注解 @CheckCase

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
public @interface CheckCase {String message() default "字符串必须是大写或小写";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};CaseMode value();
}

10.2 自定义验证业务逻辑类
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {private CaseMode caseMode;@Overridepublic void initialize(CheckCase constraintAnnotation) {// 获取约束注解的值this.caseMode = constraintAnnotation.value();}@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {// 如果值为空,则返回trueif (value == null) {return true;}// 根据caseMode的值,判断value是否需要转换大小写if (caseMode == CaseMode.UPPER) {return value.equals(value.toUpperCase());} else {return value.equals(value.toLowerCase());}}
}

10.3 自定义校验注解使用

①在Car实体类上添加注解

@Data
public class Car {//车牌号@CheckCase(value = CaseMode.UPPER,message = "车牌号必须为大写")private  String brand;//颜色@CheckCase(value = CaseMode.LOWER,message = "颜色必须为小写")private  String color;
}

②在controller 中校验参数

@GetMapping ("/car")
public ResponseEntity<String> validatorCar(@Valid @RequestBody Car car) {return ResponseEntity.ok("参数校验成功");
}

10.4 测试一下

①入参正确情况

{"brand":"云A.888888","color":"red"
}

入参正确情况

②入参错误情况

{"brand":"云a.888888","color":"RED"
}

入参错误情况

文章转载自:xiezhr

原文链接:https://www.cnblogs.com/xiezhr/p/18093602

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

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

相关文章

Linux之文件管理与重定向

文件的管理 最开始说到过, 一个进程是可以打开多个文件的并且可以对这些文件做出不同的操作, 也就是说加载到内存中的文件可能存在多个. 操作系统要不要管理这些打开的文件呢? 当我们在程序里面打开多个文件时, 操作系统肯定是得对这些文件进行管理的, 而管理的本质就是对数…

【MySQL】存储过程、存储函数、触发器

目录 存储过程介绍技术背景存储过程的作用与优势存储过程跟自定义函数很像。它们的区别是&#xff1a; 存储过程的缺点存储过程的特性基本存储过程使用1.创建语法语法说明&#xff1a;使用案例1.创建获取新闻类别数量的存储过程2.创建获取指定新闻类别ID下新闻数量的存储过程 2…

【微服务-网关】SpringCloud GateWay核心技术

在前面的文章中我们介绍了微服务网关的基础知识&#xff0c;了解了什么是网关&#xff0c;网关有什么作用&#xff0c;以及市面上有哪些成熟的网关产品&#xff0c;最后了解了网关的配置技巧。通过上篇文章&#xff0c;大家应该可以在微服务架构中完成网关的基本配置。 但是&am…

2022 年甘肃省职业院校技能大赛 高职组 网络系统管理竞赛 网络构建模块试题

2022 年甘肃省职业院校技能大赛 高职组网络系统管理竞赛 网络构建模块试题 目 录 考试说明… 3 任务描述… 3 任务清单… 3 &#xff08;一&#xff09;基础配置… 3 &#xff08;二&#xff09;有线网络配置… 4 &#xff08;三&#xff09;无线网络配置… 6 &#xff08;四&a…

老大语录六 谈产品规划

老大语录六 谈产品规划 产品经理一个重要的职责就是做产品规划。它是考验产品经理对市场和需求的阅读能力,是决定一个产品在市场上跟竞争对手分出高下的必要条件,是最能体现产品经理水平的工作。 然而我们往往在执行过程中在这一步上做的并不是特别好。虽然很多企业都会容易建…

2024 年 5 款适用于 Linux 的参考文献管理软件

时间是宝贵的&#xff0c;因此如果某款软件能让您摆脱必须执行的日常繁琐任务&#xff0c;那么它就会派上用场。 参考文献管理工具就是此类软件的典型代表&#xff0c;只需点击几下就能自动格式化引文。学生、教育工作者、作家、科学家和研究人员一定会发现它们非常有用。 在…

NFTScan | 03.18~03.24 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。 周期&#xff1a;2024.03.18~ 2024.03.24 NFT Hot News 01/ NFT 系列 NodeMonkes 地板价已超越 BAYC 3 月 18 日&#xff0c;据数据显示&#xff0c;NFT 系列 NodeMonkes 地板价已超越 Bored Ape Yacht …

9.2024

使用冒泡排序给{10 ,1,35,61,89,36,55}排序 代码&#xff1a; public class 第九题 {public static void main(String[] args) {int a[]{10,1,35,61,89,36,55};for (int i0;i<a.length-1;i){for (int j0;j<a.length-1;j){if (a[j]>a[j1]){int temp0;tempa[j];a[j]a[…

光致发光荧光量子产率测试光纤光谱仪

光致发光荧光量子产率测试系统是一种用于测量材料发光效率的高精度设备&#xff0c;它通过光致发光方法来确定样品的发射效率。光致发光荧光量子产率测试系统不仅提供了一种高效、可靠的测量手段&#xff0c;而且对于提升科学研究和工业应用中的发光材料性能具有重要作用。通过…

nacos集群搭建实战

集群结构图 初始化数据库 Nacos默认数据存储在内嵌数据库Derby中&#xff0c;不属于生产可用的数据库。官方推荐的使用mysql数据库&#xff0c;推荐使用数据库集群或者高可用数据库。 首先新建一个数据库&#xff0c;命名为nacos&#xff0c;而后导入下面的SQL&#xff08;直…

日常刷题之77-组合

题目 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案 提示&#xff1a;假设 n5,k3 就是需要组合出来&#xff0c;长度3且内容数据是在[1,n]这个区间内的所有可能得组合 同时一个组合里面内个数字只能出现一次&#…

日增500粉的全自动引流神器

全自动引流&#xff0c;采集曝光一体每天截流500&#xff0c;这是每个企业主和网红的终极梦想。今日&#xff0c;我将分享一套高效而可行的引流策略&#xff0c;帮助你实现这个目标。 我们需要理解&#xff0c;全自动引流并不意味着无人参与。相反&#xff0c;它仍需要你的参与…

【王道训练营】第6题 输入一个整型数,判断是否是对称数,如果是,输出yes,否则输出no

文章目录 我的代码改正代码其他代码 我的代码 没有完成 #include<stdio.h> int main(){int a;int b;int c0;//位数int d0;//比较几次scanf("%d",&a);while(b!0){bb/10;c;}dc/2;//比较几次int ffor(int i0 ;i<d;i){int ec;//位数fa - a / (((e-i-1)*10…

算法笔记~—位运算

目录 常见位运算&#xff1a; 1、基础位运算 2、对于一个数n。确定、修改这个数n二进制x位。 3、提取&#xff08;确定&#xff09;一个数n最右侧的1&#xff08;bit&#xff09;与干掉最右侧的1&#xff08;bit&#xff09; 4、异或运算律 5、位运算的优先级&#xff1a…

Go --- Go语言垃圾处理

概念 垃圾回收&#xff08;GC-Garbage Collection&#xff09;暂停程序业务逻辑SWT&#xff08;stop the world&#xff09;程序根节点&#xff1a;程序中被直接或间接引用的对象集合&#xff0c;能通过他们找出所有可以被访问到的对象&#xff0c;所以Go程序的根节点通常包括…

虚拟机Linux-openEuler硬盘空间扩容

虚拟机Linux-openEuler硬盘空间扩容 1、需求场景 我们在使用虚拟机时&#xff0c;可能会出现磁盘空间不够用导致各种bug出现的情况。 首先&#xff0c;我们要扩展虚拟机的可用磁盘空间。如图所示&#xff0c;我的原本硬盘大小为8G&#xff0c;我们扩展到30GB 2、打开虚拟机…

【git分支管理策略】如何高效的管理好代码版本

目录 1.分支管理策略 2.我用的分支管理策略 3.一些常见问题 1.分支管理策略 分支管理策略就是一些经过实践后总结出来的可靠的分支管理的办法&#xff0c;让分支之间能科学合理、高效的进行协作&#xff0c;帮助我们在整个开发流程中合理的管理好代码版本。 目前有两套Git…

【GameFramework框架内置模块】16、配置(Setting)

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址QQ群&#xff1a;398291828 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 【GameFramework框架】系列教程目录&#xff1a;…

2024年软件测试,“我“从初级到高级进阶,不再走弯路...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 现在2024年&#…

54、Qt/对话框、事件机制相关学习20240325

一、完善对话框&#xff0c;点击登录按钮&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&#…