Java后端校验总结
后端校验注解一直在用,但是感觉不是特别清楚,希望通过写这篇文章搞清楚。
Spring自带的Validation校验框架
Spring提供了Validator接口来校验对象,主要涉及到的方法和类如下:
- supports方法:设置校验器能对哪些对象进行校验;
- validate方法:对要校验的对象进行校验,并将校验错误记录在errors中;
- Errors类:用来存放错误信息的接口。Errors对象包含一系列的FieldError和ObjectError对象。FieldError表示与被获取的对象中的某一属性相关的一个错误;
- ValidationUtils:校验工具类,提供多个给Errors对象保存错误的方法;LocalValidatorFactoryBean:该类实现了Spring的Validator接口,也实现了JSR 303的Validator接口;
- BeanValidationPostProcessor:它就是个普通的BeanPostProcessor。它能够去校验Spring容器中的Bean,从而决定允不允许它初始化完成;
示例代码
实体类
public class MyUser implements Serializable {
private String username;
private Integer age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
}
UserValidator
/**
* 用户数据校验类
*/
@Component
public class UserValidator implements Validator {
/**
* 对哪个类进行校验,此处仅对User类进行校验
* @param clazz
* @return
*/
@Override
public boolean supports(Class<?> clazz) {
return User.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
//对name进行校验
ValidationUtils.rejectIfEmpty(errors, "username", "500", "name is empty");
MyUser user = (MyUser) target;
if (user.getAge() < 0) {
//
errors.rejectValue("age", "500", "年龄不能小于0");
}
}
}
Controller中的代码
@Autowired
private UserValidator userValidator;
@RequestMapping("/test3")
public void test1(MyUser user, Errors errors) {
userValidator.validate(user, errors);
if (errors.hasErrors()) {
//如果校验出错,输出错误信息
List<ObjectError> allErrors = errors.getAllErrors();
for (ObjectError error : allErrors) {
System.out.println("test1 error " + error.getCode() + " " + error.getDefaultMessage());
}
}
}
JSR303
JSR 303是Java为Bean数据合法性校验提供的标准框架,已经包含在Java EE 6.0中。JSR是一个规范,它的核心接口是Validator,该接口根据目标对象类中所标注的校验注解进行数据校验,并得到校验结果。
JSR303包含的注解如下:
示例代码
BindingResult:BindingResult扩展了Errors接口,同时可以获取数据绑定结果对象的信息。@Valid和BindingResult参数是成对出现的,并且在形参中出现的顺序是固定的,一前一后。
public class MyUser implements Serializable {
@NotNull(message = "username is empty")
@NotEmpty(message = "username is empty")
private String username;
@NotNull(message = "age is empty")
@Max(value = 150L, message = "年龄最大为150")
@Min(value = 0L, message = "年龄最小为0")
private Integer age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
}
@RequestMapping("/test4")
public void test4(@Valid MyUser myUser, BindingResult result) {
if (result.hasErrors()) {
List<ObjectError> allErrors = result.getAllErrors();
for (ObjectError error : allErrors) {
System.out.println("error: " + error.getCode() + " " + error.getDefaultMessage());
}
}
}
方法级别的参数校验
JSR和Hibernate validator的校验只能对Object的属性进行校验,不能对单个的参数进行校验,spring 在此基础上进行了扩展,添加了MethodValidationPostProcessor拦截器,可以实现对方法参数的校验。
1、注入Bean
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
2、在controller上添加@Validated注解
3、方法参数上加上注解校验
@RequestMapping("/test5")
public void test5(@NotNull(message = "username is empty") @NotEmpty(message = "username is empty")
@RequestParam String username, @NotNull(message = "age is empty") @Max(value = 150L, message = "年龄最大为150")
@Min(value = 0L, message = "年龄最小为0") @RequestParam Integer age) {
System.out.println("username: " + username + " age: " + age);
}
分组校验
1、定义两个分组
public interface MyUserAddVo {
}
public interface MyUserUpdateVo {
}
2、指定分组,age属性的@NtoNull注解指定了MyUserAddVo.class, MyUserUpdateVo.class两个分组,而@Min指定了一个分组
public class MyUser implements Serializable {
@NotNull(groups = {MyUserAddVo.class, MyUserUpdateVo.class}, message = "username is empty")
@NotEmpty(groups = {MyUserAddVo.class, MyUserUpdateVo.class}, message = "username is empty")
private String username;
@NotNull(groups = {MyUserAddVo.class, MyUserUpdateVo.class}, message = "age is empty")
@Max(groups = {MyUserAddVo.class, MyUserUpdateVo.class}, value = 150L, message = "年龄最大为150")
@Min(groups = {MyUserAddVo.class}, value = 0L, message = "年龄最小为0")
private Integer age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
}
3、在Controller中具体指定使用哪个分组的校验,代码中指定使用MyUserUpdateVo分组
/**
* 备注:此处@Validated(PersonAddView.class) 表示使用PersonAndView这套校验规则,若使用@Valid 则表示使用默认校验规则,
*若两个规则同时加上去,则只有第一套起作用
*
**/
@RequestMapping("/test6")
public void test6(@Validated({MyUserUpdateVo.class}) MyUser myUser) {
System.out.println("username: " + myUser.getUsername() + " age: " + myUser.getAge());
}
@Valid和@Validated注解的区别
这两个注解都是进行数据校验的标志,区别如下:
- @Valid是JSR-303标准规定的;@Validated是Spring校验框架提供的;
- @Valid可以在方法/成员变量/构造函数/方法参数上使用;@Validated可以在类/方法/方法参数上使用;区别在于成员变量上是否可以使用;由于@Valid可以在成员变量上使用,因此可以嵌套校验;
- @Valid会把校验不通过的信息交给BindingResult中,因此在controller的方法中,@Valid和BindingResult要同时存在;
- @Validated可以在类上使用;可以配合MethodValidationPostProcessor实现校验基本数据包装类型和String类型等单独的对象
参考
- 详述Spring对Bean Validation支持的核心API:Validator、SmartValidator、LocalValidatorFactoryBean…【享学Spring】
- 《SpringMVC+MyBatis快速开发与项目实战 》
- Spring数据校验(LocalValidatorFactoryBean和MethodValidationPostProcessor的区别/@Valid和@Validated的区别)
- Java Bean Validation 最佳实践