hibernate-validator校验框架
一、传统的参数校验
public class TraditionalTest {
@Test
public void test01(){
UserInfo userInfo = new UserInfo();
validateUserInfo(userInfo);
}
private static void validateUserInfo(UserInfo userInfo){
// 用户名校验
String name = userInfo.getName();
if (name == null || "".equals(name) || "".equals(name.trim())) {
//不符合校验规则
throw new RuntimeException("name 不符合校验规则");
}
// age校验
Integer age = userInfo.getAge();
boolean ageValidate = age > 1 && age < 200;
if (!ageValidate) {
throw new RuntimeException("age不符合校验规则,应在(1-200)");
}
//......
}
}
二、非web环境下使用hibernate-validator
2.1 搭建环境
<!--引入hibernate-validator-->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>7.0.1.Final</version>
</dependency>
<!--el 规范和Tomcat的实现,用于解析messages里面的表达式-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>10.0.22</version>
</dependency>
2.2 validator初体验
-
编写 ValidationUtil 工具类
package com.zs.validation; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; import jakarta.validation.Validator; import java.util.List; import java.util.Set; import java.util.stream.Collectors; public class ValidationUtil { private static Validator validator; static { validator = Validation.buildDefaultValidatorFactory().getValidator(); } public static List<String> valid(Object obj) { //如果被校验对象 没有校验通过,则set里面就有校验信息 Set<ConstraintViolation<Object>> set = validator.validate(obj); List<String> list = set.stream().map(v -> "属性:" + v.getPropertyPath() + ",属性的值" + v.getInvalidValue() + ",校验不通过的提示信息:" + v.getMessage()) .collect(Collectors.toList()); return list; } }
-
给需要校验的类添加注解
import jakarta.validation.constraints.NotNull; @Data public class UserInfo { /** * 不能是null, "", " " */ @NotNull private String name;
-
测试验证
public class Demo { public static void main(String[] args) { UserInfo userInfo =new UserInfo() ; List<String> list = ValidationUtil.valid(userInfo); System.out.println(list); } }
2.3 validator加载原理
2.3.1 javaEE规范
- javaEE规范是很多不相关的java package组成的javaee规范
- 常见的javaEE规范
- javax.sql ---- mysql ,sqlserver,oracle …
- javax.jms ---- activemq
- javax.servlet ---- tomcat,jetty …
- javax.persistence ---- hibernate
- javax.transaction----分布式事务
- java.validation ----- hibernate-validator
jdk自带了一些常用的javaee规范,对于没有自带的如果想要使用就需要自己引用了,比如beanvalidation
2.3.2 SPI机制
- spi全称为 (Service Provider Interface),是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的 机制,一种解耦非常优秀的思想。
- spi的工作原理: 就是ClassPath路径下的META-INF/services文件夹中, 以接口的全限定名来命名文件名,文件里面写该接口的实现。然后再资源加载的方式,读取文件的内容(接口实现的全限定名), 然后再去加载类。
- spi可以很灵活的让接口和实现分离, 让api提供者只提供接口, 第三方来实现。
2.3.3 源码解析
2.4 常用的校验约束注解
@Null //被注释的元素必须为null
@NotNull //被注释的元素必须不为null
@NotEmpty //被注释的集合(size > 0)/字符串(!=null && !"")
@NotBlank //!=null && !"" && !" "
@AssertTrue //被注释的元素必须为true
@AssertFalse //被注释的元素必须为false
@Min(value) //被注释的元素必须是一个数字,>=
@Max(value) //被注释的元素必须是一个数字,<=
@DecimalMin(value) >=
@DecimalMax(value) <=
@Size(max,min) //被注释的元素的大小必须在指定的范围内
@Digits(integer,fraction) //被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past //被注释的元素必须是一个过去的日期
@PastOrPresent //时间
@NegativeOrZero //<=0
@Future //被注释的元素必须是一个将来的日期
@Pattern(value) //被注释的元素必须符合指定的正则表达式
@Email // 被注释的元素必须是电子邮箱地址
2.5 约束和校验类的绑定原理
@NotNull
NotNullValidator 类校验 NotNull注解
同理 @Xxx注解的校验器类为XxxValidator
org.hibernate.validator.internal.metadata.core.ConstraintHelper
if(enabledBuiltinConstraints.contains(BuiltinConstraint.JAKARTA_VALIDATION_CONSTRAINTS_NOT_BLANK)) {
putBuiltinConstraint(tmpConstraints, NotBlank.class, NotBlankValidator.class);
}
注意:一个注解约束可能对应多个约束Validator,如@NotEmpty
2.6 自定义校验规则
-
自定义注解
@Constraint(validatedBy = {UserStateValidator.class }) @Target({ FIELD}) @Retention(RUNTIME) public @interface UserState { String message() default ""; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; }
-
自定义注解校验器
/** * @description 用户状态验证器 */ public class UserStateValidator implements ConstraintValidator<UserState,Integer> { @Override public boolean isValid(Integer value, ConstraintValidatorContext context) { //如果用户状态为空 或者为1 校验失败 if ( null==value || 1!=value) { return false; } return true; } }
2.7 分组校验
public class UserInfo {
public interface Add{}
public interface Update{}
// 只用于新增校验
@NotNull(groups = {Add.class})
private String name;
private Integer age;
}
public class ValidationUtil2 {
private static Validator validator;
static {
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
public static List<String> valid(Object obj, Class<?>... groups) {
//如果被校验对象 没有校验通过,则set里面就有校验信息
Set<ConstraintViolation<Object>> set = validator.validate(obj,groups);
List<String> list = set.stream().map(v ->
"属性:" + v.getPropertyPath() +
",属性的值" + v.getInvalidValue() +
",校验不通过的提示信息:" + v.getMessage() +
",消息模板(为被替换的提示信息):" + v.getMessageTemplate()
)
.collect(Collectors.toList());
return list;
}
}
2.8 @Valid级联校验
public class Grade {
//班级号
@NotBlank
private String no;
}
public class UserInfo {
public interface Add{}
public interface Update{}
// 只用于新增校验
@NotNull(groups = {Add.class})
private String name;
private Integer age;
@NotNull
@Valid //被引用对象加@Valid注解才可以完成级联校验
private Grade grade;
}
三、web环境中使用下使用hibernate-validator
3.1 搭建springboot环境
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.7.7</version>
</dependency>
<!--web组件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.7</version>
</dependency>
3.2 使用@Valid自动校验
-
编程式校验
/** * 编程式校验 * * @param userInfo 用户信息 * @return {@link String} */ @GetMapping("/addUser") public String addUser(UserInfo userInfo){ List<String> result = ValidationUtil.valid(userInfo); System.out.println(result); if (result.size() > 0) { return "failed"; } else { return "success"; } }
-
注解式校验
@GetMapping("/addUser2") public String addUser2(@Valid UserInfo userInfo, BindingResult bindingResult){ if (bindingResult.hasErrors()) { //判断是不是满足约束 List<ObjectError> allErrors = bindingResult.getAllErrors(); for (ObjectError error : allErrors) { System.out.println(error.getObjectName() + "::" + error.getDefaultMessage()); } //获取没通过校验的字段详情 List<FieldError> fieldErrors = bindingResult.getFieldErrors(); for (FieldError fieldError : fieldErrors) { System.out.println(fieldError.getField() + ":" + fieldError.getDefaultMessage() + ",当前没通过校验规则的值是:" + fieldError.getRejectedValue()); } } return "ok"; }
3.3 使用@Validated 自动校验
/**
* 编程式校验
*
* @param userInfo 用户信息
* @return {@link String}
*/
@GetMapping("/addUser3")
public String addUser3(@Validated(value={UserInfo.Add.class}) UserInfo userInfo, BindingResult bindingResult){
List<String> result=new ArrayList<>();
if (bindingResult.hasErrors()) { //判断是不是满足约束
List<ObjectError> allErrors = bindingResult.getAllErrors();
//获取没通过校验的字段详情
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
for (FieldError fieldError : fieldErrors) {
String s= fieldError.getField() + ":" + fieldError.getDefaultMessage()
+ ",当前没通过校验规则的值是:" + fieldError.getRejectedValue();
System.out.println(s);
result.add(s);
}
}
return result.toString();
}
@Validated 和 @Valid的区别
-
@Validated可以指定分组
-
@validated支持方法参数的自动校验
@RestController @Validated //表示整个类都启用校验,如果碰到入参含有bean validation 注解的话,就会自动校验 public class UserInfoHandler { @GetMapping("/getByName") public String getByName(@NotNull String name){ return name + "ok"; }
3.4 统一异常处理
-
控制层代码
/** * 最终版本 * @param userInfo * @return */ @GetMapping("/addUser4") public AjaxResult addUser4(@Validated(value={UserInfo.Add.class}) UserInfo userInfo){ //TODO 后续业务处理 return AjaxResult.success(); }
-
全局异常处理器
/** * 全局异常处理器 */ @RestControllerAdvice public class GlobalExceptionHandler { /** * 自定义验证异常 */ @ExceptionHandler(BindException.class) public AjaxResult handleBindException(BindException e) { String message = e.getAllErrors().get(0).getDefaultMessage(); return AjaxResult.error(message); } }