Java代码瘦身,巧用 @Valid,@Validated 的分组校验和嵌套检验,实现高阶参数校验操作
创始人
2025-05-28 22:14:05
0

导读

        在 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 bookNames;@NotEmpty@Size(min = 1, message = "老师不能没有学生", groups = TeacherWithDefault.class)private List 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 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 list = new ArrayList<>();List 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"。

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


相关内容

热门资讯

师德师风学习材料 师德师风学习材料  师德师风学习材料(精选8篇)  师德建设决定我国教师队伍建设的成败,也就决定我国...
励志成才优秀大学生事迹材料 励志成才优秀大学生事迹材料(精选12篇)  在学习、工作乃至生活中,大家都不可避免地要接触到事迹材料...
收入证明范本 收入证明范本  收入证明范本(精选16篇)  在生活、工作和学习中,大家都不可避免地要接触到证明吧,...
骨干教师申报事迹材料 骨干教师申报事迹材料(通用5篇)  在日常生活或是工作学习中,大家都尝试过写事迹吧,根据范围的不同,...
党史教育组织生活会个人对照检... 党史教育组织生活会个人对照检查材料  党史教育组织生活会个人对照检查材料(精选18篇)  组织生活一...
工作承诺书 【实用】工作承诺书三篇  在生活中,接触并使用承诺书的人越来越多,不同的承诺书内容同样也是不同的。那...
感动中国人物的事迹 感动中国人物的事迹  感动中国人物的事迹(精选27篇)  “感动中国”节目被媒体誉为“中国人的年度精...
家访记录内容学生在家表现 家访记录内容学生在家表现  一、家访的重要性  1、家访是“教育即服务”的现代教育观的具体表现,是一...
建党100周年200字短文 建党100周年200字短文  建党100周年  2021年7月1日是中国共产党成立100周年纪念日。...
讲党课材料 讲党课材料  党课是以上课讲授的形式进行党的基本知识教育。以下是小编为大家收集的讲党课材料(精选5篇...
优秀少先队员事迹材料 【推荐】优秀少先队员事迹材料(精选20篇)  在学习、工作、生活中,要用到事迹材料的情况还是蛮多的,...
预备党员四次思想汇报 预备党员四次思想汇报  一、思想汇报的主要内容  对党的性质、纲领和路线、方针、政策的认识;  学习...
疫苗接种志愿者简报 疫苗接种志愿者简报14篇  在现在的社会生活中,越来越多地方需要用到简报,简报一般都有固定的报头,包...
党员对党支部的意见和建议 党员对党支部的意见和建议  支部要不断激励职工,争做先进、努力工作,在实践工作中不断完善和提高自己。...
预备党员发言稿 预备党员发言稿  一、发言稿的分类  (1)开幕词。指比较隆重的大型会议开始时所用的讲话稿。  (2...
组织生活会学习材料 组织生活会学习材料  一、民主生活会制度  民主生活会制度作为群众路线教育实践活动重要环节,是在加强...
天冷的说说 关于天冷的说说  随着在线社交网络的爆炸式增长,越来越多人热衷于在朋友圈发布说说,用以记录日常生活。...
家庭和睦最美家庭事迹材料 家庭和睦最美家庭事迹材料(通用11篇)  无论在学习、工作或是生活中,大家都不可避免地会接触到事迹材...
最美教师感人事迹材料 最美教师感人事迹材料(精选17篇)  在平平淡淡的学习、工作、生活中,大家都尝试过写事迹材料吧,从先...
结婚请柬 结婚请柬模板一份精美的结婚请柬,是你们婚礼成功的第一步,所以一点也不能马虎,那么究竟选怎么样的结婚请...