使用JSR303对数据进行校验【JAVA】
前言
1、SpringBoot项目中Controller的validator做参数校验不生效的问题
解决:
springboot 2.3之前的集成在spring-boot-starter-web
里了,所以不需要额外引入包
springboot 2.3之后需要引入 spring-boot-starter-validation
简单校验
流程:
①在需要校验的属性上添加校验注解
②在controller方法内,在对应的实体类前面添加 @Valid 注解,开启属性校验功能
代码:
public class UserEntity {
@Min(value = 0,message = "年龄不能小于0!") //@Min该注解表示age的值要大于或等于0
private Integer age;
@NotBlank(message = "用户名不能为空!") //@NotBlank注解表示该属性不能为空
private String username;
@NotBlank(message = "密码不能为空!")
@Length(min = 6,message = "密码不能低于6位!") //@Length该注解表示长度要大于6
private String password;
private String nickname;
}
/**
* 简单的校验规则
*/
@RestController
public class SimpleValidRule {
/**
* @param user 要校验的实体类
* @param result 如果不满足校验规则,可以通过该类接受异常信息
* @return
* @Valid 开启数据校验功能
*/
@RequestMapping("/simpleSave")
private Map saveUser(@Valid @RequestBody UserEntity user, BindingResult result) {
Map<String, String> map = new HashMap<>();
if (result.hasErrors()) {
result.getFieldErrors().forEach(item -> {
//获取失败信息
map.put(item.getField(), item.getDefaultMessage());
});
} else {
map.put("data", "数据校验成功");
}
return map;
}
}
分组校验
ps:上面通过简单案例演示了数据校验,但是可以看到属性校验返回的异常信息是跟业务代码写在一起,这样看起来非常不优雅,同时也不利于后面维护,所以对于异常信息,我们应该使用一个全局异常处理。
流程:
①还是在需要校验属性的上面添加校验规则,但是这里还添加了一个分组属性。想象一下,如果执行save方法时,我们必须要求用户输入username属性,执行update方法时,必须用户传入age属性,那如果通过上面校验,发现并不能满足这种要求,所以这就是分组的意义。
②在controller方法内,通过注解@Validated开启属性校验,通过指定要校验的规则,如果不指定,则跟上面简单校验一样。
代码:
public class UserEntity2 {
//这里可以看到,该属性校验多了一个groups分组属性
@Min(value = 0, message = "年龄不能小于0!", groups = UpdateGroup.class)
private Integer age;
//可以看到,username跟前面的age属性定义了不同组
@NotBlank(message = "用户名不能为空!", groups = SaveGroup.class)
private String username;
@NotBlank(message = "密码不能为空!")
@Length(min = 6, message = "密码不能低于6位!")
private String password;
@NotBlank(message = "用户昵称不能为空", groups = {SaveGroup.class})
private String nickname;
}
/**
* 指定分组校验
*/
@RestController
public class GroupValidRule {
//这里通过@Validated注解定义了只校验那个分组下的属性,其它分组,包括没写的,不会进行校验
@PostMapping("/groupSave")
private String saveUser(@Validated(value = SaveGroup.class) @RequestBody UserEntity user) {
/* 执行一系列流程... */
return "数据校验成功";
}
@PostMapping("/groupUpdate")
private String updateUser(@Validated(value = UpdateGroup.class) @RequestBody UserEntity user) {
/* 执行一系列流程... */
return "数据校验成功";
}
}
/**
* 全局异常处理类
*/
@RestControllerAdvice
public class GlobalException {
@ExceptionHandler
public String handlerException(Exception e){
return "数据校验失败!";
}
}
上面如果执行saveUser方法,因为使用注解@Validated(value = SaveGroup.class)定义了只校验SaveGroup分组下的属性,所以只有username属性会被进行校验!
自定义校验
可能以上校验规则并不一定符合我们,所以这时候可以通过自定义校验规则
流程:
①new一个自定义注解类
②创建具体的校验规则类,需要实现ConstraintValidator接口,重写两个方法:initialize()和isValid()
③在需要自定义校验属性上添加该注解,并指定校验规则
代码:
@Data
public class UserEntity3 {
//这里自定义校验规则,要求必须输入name的值为张三
@CustomValidAnnotation(name = "张三")
private String name;
}
/**
* 自定义校验
*/
@RestController
public class CustomValidRule {
@RequestMapping("/customSave")
private String saveUser(@Validated @RequestBody UserEntity user) {
return "属性校验成功";
}
}
/**
* 自定义校验注解
*/
@Constraint(
validatedBy = {CustomValid.class} //自定义具体的校验器,可以指定多个
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomValidAnnotation {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String name(); //添加一个name属性作为校验规则
}
/**
* 自定义校验器,需要实现ConstraintValidator并传入两个泛型,第一个是自定义校验的注解,第二个校验属性的类型
*/
public class CustomValid implements ConstraintValidator<CustomValidAnnotation, String> {
private String name;
/**
* 这个方法可以获取我们自定义校验属性的值,也就是在实体类定义属性校验规则
* @param constraintAnnotation 自定义注解
*/
@Override
public void initialize(CustomValidAnnotation constraintAnnotation) {
String name = constraintAnnotation.name(); //获取自定义注解上的value
this.name = name;
}
/**
* 这个方法是获取用户传入的数据,我们可以通过上面拿到的校验规则与用户输入进行校验匹配,判断用户输入是否满足我们要求的参数格式
* @param s 用户输入的值
* @param constraintValidatorContext
* @return 返回false表示输入的数据不合法,true则相反
*/
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (name.equals(name)){
return true;
}
return false;
}
}