spring Validated是基于java JSR303进一步封装,具有校验功能的框架,封装了原本的@Valid,但不同于@Valid的是他具有分组功能
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
代码
数据校验需要在两个位置用到spring Validated的注解,一个是在请求参数的位置,一个是在实体类的位置。
请求参数:
参数需要使用@Validated注解标注,表示该参数需要被校验
@GetMapping("/getAll")
public UserVo getAll(@Validated UserVo userVo) {
return userVo;
}
实体类:
在属性位置标注上对应的注解比如 @NotEmpty(message = “用户名不为空”),表示name不能为空,为空白字符,长度不能为0
@Data
public class UserVo {
@NotEmpty(message = "用户名不为空")
private String name;
@Min(value = 18,message = "年龄必须大于18岁")
private String age;
@Email(regexp = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$",message = "邮箱格式错误")
private String email;
}
那如果我们需要对某些字段进行校验,或者按照某种条件进行校验,我们该怎么做呢?
那么我们就需要分组校验来进行
下面我们进行分组校验测试
分组校验
分组接口
先创建两个接口,这两个接口不需要任何内容,就只是用于分组标记
public interface IGroupA {
}
public interface IGroupB {
}
请求参数
我们在请求参数出设置校验标记,
@Validated({IGroupB.class})表示只有分组为IGroupB.class才进行校验,否则不校验
@GetMapping("/getAll")
public UserVo getAll(@Validated({IGroupB.class}) UserVo userVo) {
return userVo;
}
实体类:
groups = IGroupB.class表示将这个校验标记为IGroupB分组,如果controller不是IGroupB就不进行校验。否则就校验
@Data
public class UserVo {
@NotEmpty(message = "用户名不为空", groups = IGroupA.class)
private String name;
//只有分组为IGroupB才进行校验
@NotBlank
@Min(value = 18, message = "年龄必须大于18岁", groups = {IGroupB.class})
private String age;
@NotBlank
@Email(message = "邮箱格式错误", groups = {IGroupB.class})
private String email;
}
如果访问,上面的结果是:name不会校验,age和email会进行校验。
那如果在请求参数处同时标记IGroupA.class,IGroupB.class会怎么样呢?
@GetMapping("/getAll")
public UserVo getAll(@Validated({IGroupA.class,IGroupB.class}) UserVo userVo) {
return userVo;
}
答案就是:三个都会进行校验,那如果我们对一个进行校验后发现不通过,后面的就不需要进行校验该怎么办呢?
那么我们可以采用组序列,在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。
组序列
创建一个接口:IGroup
在上面标注注解,@GroupSequence({Default.class, IGroupA.class, IGroupB.class})
import javax.validation.GroupSequence;
import javax.validation.groups.Default;
@GroupSequence({Default.class, IGroupA.class, IGroupB.class})
public interface IGroup {
}
实体类
对实体类的属性上同时标注上groups = IGroupB.class和groups = IGroupA.class)
@Data
public class UserVo {
@NotEmpty(message = "用户名不为空", groups = IGroupA.class)
private String name;
//只有分组为IGroupB才进行校验
@Min(value = 18, message = "年龄必须大于18岁", groups = {IGroupB.class})
private String age;
@Email(message = "邮箱格式错误", groups = {IGroupB.class})
private String email;
}
请求参数
在请求参数处标注一个@Validated({IGroup.class})注解
GetMapping("/getAll")
public UserVo getAll(@Validated({IGroup.class}) UserVo userVo) {
return userVo;
}
再进行测试;
如果我们三个参数都填错,那么按照顺序,他只会校验name后就不再进行校验。
那如果我们实体类中是以另一个实体类为属性,我们能对他进行校验吗?
那当然可以。
嵌套校验
创建另一个实体类Teacher
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
@Data
public class Teacher {
@NotEmpty(message = "老师姓名不能为空")
private String teacherName;
@Min(value = 1, message = "至少为1")
private int type;
}
实体类
将其封装到UserVo中
@Data
public class UserVo {
@NotEmpty(message = "用户名不为空", groups = IGroupA.class)
private String name;
//只有分组为IGroupB才进行校验
@Min(value = 18, message = "年龄必须大于18岁", groups = IGroupB.class)
private String age;
@Email(message = "邮箱格式错误", groups = IGroupB.class)
private String email;
@NotNull(message = "用户不能为空")
@Size(min = 1,message = "至少有一个用户")
private List<Teacher> teachers;
}
请求参数
我们访问前端接口测试
@GetMapping("/getAll")
public UserVo getAll(@Validated({IGroup.class}) UserVo userVo) {
return userVo;
}
参数为:
发现只校验了NotNull, 和 Size,并没有对teacher信息里面的字段进行校验,因为我们type给的0,那么应该校验发出校验,
这里teacher中的type明显是不符合约束要求的,但是能检测通过,是因为在student中并没有做 嵌套校验
解决办法
可以在teacherBeans中加上 @Valid,
@Data
public class UserVo {
@NotEmpty(message = "用户名不为空", groups = IGroupA.class)
private String name;
//只有分组为IGroupB才进行校验
@Min(value = 18, message = "年龄必须大于18岁", groups = IGroupB.class)
private String age;
@Email(message = "邮箱格式错误", groups = IGroupB.class)
private String email;
@Valid
@NotNull(message = "用户不能为空")
@Size(min = 1,message = "至少有一个用户")
private List<Teacher> teachers;
}
我们再次测试
发现结果对teacher类中的字段进行了校验。测试成功
将@Validated的返回信息通过全局异常类同意处理
先创建一个全局异常类
/**
* 统一异常处理
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* jsr303校验异常
* @param e
* @return
*/
@ExceptionHandler({MethodArgumentNotValidException.class})
public R validatedException(MethodArgumentNotValidException e){
BindingResult bindingResult = e.getBindingResult();
String field = bindingResult.getFieldError().getField();
if("mobile".equals(field)){
return R.error().code("30").message(bindingResult.getFieldError().getDefaultMessage());
}else{
return R.error().code("31").message(bindingResult.getFieldError().getDefaultMessage());
}
}
}
参数vo实例类
@Data
public class RegisterVo implements Serializable {
//手机号
@NotBlank(message = "手机号码不能为空")
@Pattern(regexp = "^[1][3,4,5,7,8,9][0-9]{9}$",message = "手机号码不正确")
private String mobile;
//密码
@NotBlank(message = "密码不能为空")
private String password;
//@NotBlank校验字符不为null,不为空字符串(" "),字符长度不为0("")
//@NotEmpty校验字符不为null,字符长度不为0("")
@NotBlank(message = "昵称不能为空")
private String nickname;
}
@PostMapping("register")
public R register(@RequestBody @Validated RegisterVo registerVo){
memberService.register(registerVo);
return R.ok().message("注册成功");
}
可用注解说明
@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Past 验证注解的元素值(日期类型)比当前时间早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式