Java代码瘦身,巧用 @Valid,@Validated 的分组校验和嵌套检验,实现高阶参数校验操作

news/2024/11/18 16:20:30/

导读

        在 JavaEE 项目中, RestFull 层接收参数首先要对一些字段的格式进行校验,以防止所有查询都落到数据库,这也是一种合理的限流手段。以前基本上都是用 if...else...,这样的代码太啰嗦,除了使用策略模式进行优化,今天介绍一下校验注解@Valid,@Validated和@PathVariable,不仅可以减轻代码量,还加强了代码的易读性。


正文

1. @Valid 和 @Validated 区别

        先讲一下这两个注解:@Valid与@Validated都是用来校验接收参数的,如果不使用注解校验参数,那么就需要在业务代码中逐一校验,这样会增加很多的工作量,并且代码不优美。

        刚开始接触的时候多半会被弄混,实际上二者差距还是挺大的。根据自己的项目经验,@Validated和@Valid各有特点,可以联合使用。

  • 提供者

javax.validation.Valid:使用 Hibernate validation 的时候使用,是 JSR-303 规范标准注解支持。如果你是 springboot 项目,那么可以不用单独引入依赖了,因为它就存在于最核心的 web 开发包(spring-boot-starter-web)里面;

org.springframework.validation.annotation.Validated:只用 Spring Validator 校验机制使用,是 Spring 做得一个自定义注解,增强了分组功能;

  • 标注位置

@Validated:可以用在类型、方法和方法参数上,不能用于成员属性(field)上。如果注解在成员属性上,则会报不适用于field的错误;                  

@Valid:可以用在方法、构造函数、方法参数和成员属性(field)上;

  • 分组支持

@Validated:提供分组功能,可以在参数验证时,根据不同的分组采用不同的验证机制;

@Valid:没有分组的功能,不能进行分组校验;

  • 嵌套支持

@Validated:不能进行嵌套对象校验;

@Valid:可以进行嵌套校验,但是,需要在嵌套的字段上面加上注解

2. 常用的校验方法

  • Debug进入jar包,可以看到全量的相关注解:

  • 简述一些常用注解:
注解使用方法
@AssertFalse被校验的对象必须为 true
@AssertTrue被校验的对象必须为 false。
@DecimalMax(value = "val")被校验的对象必须是数字,而且小于等于 val
@DecimalMin(value = "val")被校验的对象必须是数字,而且大于等于 val
@Digits(integer = in, fraction = fra)校验字符串是否是符合指定格式的数字:in 指定整数精度,fra 指定小数精度。
@Future被校验的对象(日期类型)必须是将来时间,即:比当前时间晚。
@Past:被校验的对象(日期类型)必须是过去时间,即:比当前时间早。
@Size(min = min, max = max)元素值的在 min 和 max(包含)指定区间之内,如字符长度、集合大小(对于集合来说,null 和空字符串都是算长度的)。

@NotBlank

所注解的元素不能为null且不能为空白,并且必须至少包含一个非空白字符,用于校验CharSequence(含String、StringBuilder和StringBuffer)。只支持字符类型。


@NotEmpty
所注解的元素不能为null且长度大于0,可以是空白,用于校验 CharSequence、数组、Collection 和 Map。

@NotNull
所注解的元素不能为 null,接受任何类型。

@Null
所注解的元素必须为 null,接受任何类型。
@Pattern(regexp = "正则表达式", message = "")

所注解的元素必须匹配指定的正则表达式。

注意:如果 @Pattern 所注解的元素是null,则@Pattern 注解会返回 true,即也会通过校验,所以应该把 @Pattern 注解和 @NotNull 注解结合使用。

3. @Validated分组校验

场景:多个 Restfull 接口共用一个标准 Bean,每个接口的参数相同,但是需要校验的参数(必输项)却不完全相同,这样的场景可以使用 @Validated,因为它提供了分组校验的功能。

分组说明

隐式分组

1.没有显式分组的默认都是 Default 组;

2.显式分组之后,剩下的那些没有被划分到自建组的字段都属于 Default 组;

3.平常我们写 @Validated注解的时候,不写分组的话默认就是 @Validated(group = {Default.class});

显式分组

1.自定义interface接口的分组,属于自建组;

2.自建组可以继承 Default.class,也可以不继承 Default.class,两者意义不同;

3.多个分组可以一起实用;

4.分组机制让我们可以很灵活的使用对象里面的某些字段,以实现高权限等级参数传递校验等操作。

  • 新建请求对象

@Data
public class TeacherDTO {@NotBlank(message = "id必传")private String id;@NotBlank(message = "不能没有名称")private String name;@NotNull(message = "age必传")private Integer age;@NotBlank(message = "不能没有idCard")private String idCard;@NotBlank(message = "老师不能没有手机号", groups = OnlyTeacher.class)private String phone;@NotEmpty(message = "学生不能没有书")@Size(min = 2, message = "学生必须有两本书", groups = OnlyStudent.class)private List<String> bookNames;@NotEmpty@Size(min = 1, message = "老师不能没有学生", groups = TeacherWithDefault.class)private List<String> studentList;
}
  • 新建分组

// Teacher分组
public interface TeacherValid { }// Student分组
public interface StudentValid { }// 继承Default的分组
public interface OtherValid extends Default{ }
  • 接口测试

/*** Created by tjm on 2022/11/11.*/
@RestController
@RequestMapping("/test")
public class TestValidController {private static final Logger LOGGER = LoggerFactory.getLogger(TestValidController.class);/*** 测试 - 分组校验 - 默认default*/@PostMapping("/only/default")public Object testDefaultValid(@Validated TeacherDTO param, BindingResult bindingResult) {if (bindingResult.hasErrors()) {return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());}return ResultGenerator.genSuccessResult();}/*** 测试 - 分组校验 - 只有teacher*/@PostMapping("/only/teacher")public Object testOnlyTeacherValid(@Validated(OnlyTeacher.class) TeacherDTO param, BindingResult bindingResult) {if (bindingResult.hasErrors()) {return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());}return ResultGenerator.genSuccessResult();}/*** 测试 - 分组校验 - 只有student*/@PostMapping("/only/student")public Object testOnlyStudentValid(@Validated(OnlyStudent.class) TeacherDTO param, BindingResult bindingResult) {if (bindingResult.hasErrors()) {return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());}return ResultGenerator.genSuccessResult();}/*** 测试 - 分组校验 - teacher + default*/@PostMapping("/with/teacher")public Object testWithTeacherValid(@Validated({OnlyTeacher.class, Default.class}) TeacherDTO param, BindingResult bindingResult) {if (bindingResult.hasErrors()) {return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());}return ResultGenerator.genSuccessResult();}/*** 测试 - 分组校验 - 继承default*/@PostMapping("/with/default")public Object testWithDefaultValid(@Validated(TeacherWithDefault.class) TeacherDTO param, BindingResult bindingResult) {if (bindingResult.hasErrors()) {return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());}return ResultGenerator.genSuccessResult();}
}
  • 结果

分组校验参数
只有 defaultid、name、age、idCard
只有 teacherphone
只有 studentbooknames
teacher + defaultid、name、age、idCard、phone
teacher 继承 defaultid、name、age、idCard、studentList

4.@Valid嵌套校验

  • 新建请求对象
public class Item {@NotNull(message = "id不能为空")@Min(value = 1, message = "id必须为正整数")private Long id;// 嵌套验证必须用 @Valid@Valid             @NotNull(message = "props不能为空")@Size(min = 1, message = "props至少要有一个自定义属性")private List<Prop> props;
}public class Prop {@NotNull(message = "pid不能为空")@Min(value = 1, message = "pid必须为正整数")private Long pid;@NotNull(message = "vid不能为空")@Min(value = 1, message = "vid必须为正整数")private Long vid;@NotBlank(message = "pidName不能为空")private String pidName;@NotBlank(message = "vidName不能为空")private String vidName;
}
  • 接口测试
    /*** 测试 - 分组校验 - 继承default*/@PostMapping("/item")public Object testItemValid(@Validated Item param, BindingResult bindingResult) {if (bindingResult.hasErrors()) {return ResultGenerator.genFailResult(bindingResult.getFieldError().getDefaultMessage());}return ResultGenerator.genSuccessResult();}
  • 测试结果

1. 不仅校验 Item 参数,还会校验子类 Prop 参数;

2. 注意:嵌套验证必须在子参数上用 @Valid。

5.Restfull层@Validated的使用

        校验参数的时候,如何判断并返回失败的结果?一般有两种方式:

  • 全局异常捕获
@ControllerAdvice
@RestController
@Slf4j
public class GlobalExceptionHandler {/*** 非法参数验证异常*/@ExceptionHandler(MethodArgumentNotValidException.class)@ResponseStatus(value = HttpStatus.OK)public ApiResult handleMethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex) {BindingResult bindingResult = ex.getBindingResult();List<String> list = new ArrayList<>();List<FieldError> fieldErrors = bindingResult.getFieldErrors();for (FieldError fieldError : fieldErrors) {list.add(fieldError.getDefaultMessage());}Collections.sort(list);log.error("fieldErrors" + JSON.toJSONString(list));return ApiResult.fail(ApiCode.PARAMETER_EXCEPTION, list);}
}
  • 用 BindingResult 在实体类校验信息返回结果绑定

        即使是全局异常捕获的方式,也能看到:校验信息是被封装在 BindingResult 对象里的,所以,我们也可以在 RestFull 层直接取。

1. BindingResult用在实体类校验信息返回结果绑定;

2. BindingResult.hasErrors()判断是否校验通过,bindingResult.getFieldError().getDefaultMessage() 获取在 TestEntity 的属性设置的自定义message,如果没有设置,则返回默认值 "javax.validation.constraints.XXX.message"。

        可以看到,我上面的例子用的都是这种方法,我觉得这样更方便、直观,维护性更好。



http://www.ppmy.cn/news/32280.html

相关文章

机械革命极光Pro开机错误重启无法进入桌面怎么办?

机械革命极光Pro开机错误重启无法进入桌面怎么办&#xff1f;有用户正常使用机械革命极光Pro电脑开机是&#xff0c;出现了无法开机进入系统桌面的情况&#xff0c;重新启动电脑依然无法解决问题。那么这个情况怎么去进行问题的解决呢&#xff1f;一起来看看以下的解决方法分享…

华为OD机试(20222023)考点分类

字符串,数组,集合操作 题库分值序号题目考点 or 实现

docker 形态构建redis 哨兵模式集群

主从模式介绍 哨兵是 Redis 的一种运行模式&#xff0c;它专注于对 Redis 实例&#xff08;主节点、从节点&#xff09;运行状态的监控&#xff0c;并能够在主节点发生故障时通过一系列的机制实现选主及主从切换&#xff0c;实现故障转移&#xff0c;确保整个 Redis 系统的可用…

2022年全国职业院校技能大赛(中职组)网络安全竞赛试题——MYSQL安全测试解析(详细)

B-3任务三:MYSQL安全测试 *任务说明:仅能获取Server3的IP地址 1.利用渗透机场景kali中的工具确定MySQL的端口,将MySQL端口作为Flag值提交; 2.管理者曾在web界面登陆数据库,并执行了select <?php echo \<pre>\;system($_GET[\cmd\]); echo \</pre>\; ?…

我的CSDN四周年创作纪念日

机缘 记得还是上大一时候初次注册了CSDN&#xff0c;和很多大学生用这个网站的目的一样&#xff1a;搜寻能够实现老师布置的作业的代码&#xff0c;甚至是搜xxx管理系统然后改一改用来做课程设计。 后来大学毕业工作了&#xff0c;工作中也会记录遇到的问题和解决方案以及总结…

2022-2023年度广东省职业院校学生专业技能大赛 中职组网络安全赛项竞赛规程

2022-2023年度广东省职业院校学生专业技能大赛 中职组网络安全赛项竞赛规程 一、赛项名称 赛项编号&#xff1a;Z27 赛项名称&#xff1a;网络安全赛项组别&#xff1a;中职 赛项归属&#xff1a;信息技术类 二、竞赛目的 为检验中职学校网络信息安全人才培养成效&#xff0c;促…

【链表OJ题(五)】合并两个有序链表

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;数据结构 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录链表OJ题(五)1. 合并…

大学生考研的意义?

当我拿起笔头&#xff0c;准备写这个话题时&#xff0c;心里是非常难受的&#xff0c;因为看到太多的学生在最好的年华&#xff0c;在自由的大学本应该开拓知识&#xff0c;提升认知&#xff0c;动手实践&#xff0c;不断尝试和试错&#xff0c;不断历练自己跳出学生思维圈&…