编辑
2022-11-23
实用工具
00
请注意,本文编写于 862 天前,最后修改于 639 天前,其中某些信息可能已经过时。

目录

1. Hibernate Validator 简介
2. Hibernate Validator 的作用
3. Hibernate Validator 的使用
3.1 Java对象添加约束注解
3.2 API 接口入参校验
3.3 封装工具类代码中校验
4. 自定义constraint的实现
4.1 编写constraint
4.2 测试自定义校验注解
5. 分组校验的实现
5.1 Account 对象添加多套校验规则
5.2 测试分组校验
6. 常用约束规则

1. Hibernate Validator 简介

平时项目中,难免需要对参数 进行一些参数正确性的校验,这些校验出现在业务代码中,让我们的业务代码显得臃肿,而且,频繁的编写这类参数校验代码很无聊。鉴于此,觉得 Hibernate Validator 框架刚好解决了这些问题,可以很优雅的方式实现参数的校验,让业务代码 和 校验逻辑 分开,不再编写重复的校验逻辑。 Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。 Bean Validation 为 JavaBean 验证定义了相应的元数据模型和API。缺省的元数据是 Java Annotations,通过使用 XML 可以对原有的元数据信息进行覆盖和扩展。Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。

2. Hibernate Validator 的作用

验证逻辑与业务逻辑之间进行了分离,降低了程序耦合度; 统一且规范的验证方式,无需你再次编写重复的验证代码; 你将更专注于你的业务,将这些繁琐的事情统统丢在一边。

3. Hibernate Validator 的使用

项目中,主要用于接口api 的入参校验和 封装工具类 在代码中校验两种使用方式。

3.1 引入jar包

xml
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.9.Final</version> </dependency>

3.1 Java对象添加约束注解

java
package com.example.demo.bean; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; import org.springframework.format.annotation.DateTimeFormat; import java.util.Date; /** * @date 2018/4/12 11:08 */ @Data @AllArgsConstructor @NoArgsConstructor public class Account { private String id; @NotNull @Length(max = 20) private String userName; @NotNull @Pattern(regexp = "[A-Z][a-z][0-9]") private String passWord; @DateTimeFormat(pattern = "yyy-MM-dd") private Date createTime; private String alias; @Max(10) @Min(1) private Integer level; private Integer vip; }

3.2 API 接口入参校验

定义接口 接口入参 需要添加 @Valid 注解,才会对入参进行参数校验。

java
@PostMapping("/saveAccount") public Object saveAccount(@RequestBody @Valid Account account){ accountService.saveAccount(account); return "保存成功"; }

PostMan 测试接口

请求参数: requestbody:{"alias":"kalakala","userName":"wokalakala"}

测试结果 服务器返回400的状态码,响应结果中可查看到提示 passWord 字段不能为null。具体的响应报文有点长,就不贴出来啦。

3.3 封装工具类代码中校验

validationUtil 工具类

java
import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import lombok.Data; import org.hibernate.validator.HibernateValidator; public class ValidationUtil { /** * 开启快速结束模式 failFast (true) */ private static Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false).buildValidatorFactory().getValidator(); /** * 校验对象 * * @param t bean * @param groups 校验组 * @return ValidResult */ public static <T> ValidResult validateBean(T t,Class<?>...groups) { ValidResult result = new ValidationUtil().new ValidResult(); Set<ConstraintViolation<T>> violationSet = validator.validate(t,groups); boolean hasError = violationSet != null && violationSet.size() > 0; result.setHasErrors(hasError); if (hasError) { for (ConstraintViolation<T> violation : violationSet) { result.addError(violation.getPropertyPath().toString(), violation.getMessage()); } } return result; } /** * 校验bean的某一个属性 * * @param obj bean * @param propertyName 属性名称 * @return ValidResult */ public static <T> ValidResult validateProperty(T obj, String propertyName) { ValidResult result = new ValidationUtil().new ValidResult(); Set<ConstraintViolation<T>> violationSet = validator.validateProperty(obj, propertyName); boolean hasError = violationSet != null && violationSet.size() > 0; result.setHasErrors(hasError); if (hasError) { for (ConstraintViolation<T> violation : violationSet) { result.addError(propertyName, violation.getMessage()); } } return result; } /** * 校验结果类 */ @Data public class ValidResult { /** * 是否有错误 */ private boolean hasErrors; /** * 错误信息 */ private List<ErrorMessage> errors; public ValidResult() { this.errors = new ArrayList<>(); } public boolean hasErrors() { return hasErrors; } public void setHasErrors(boolean hasErrors) { this.hasErrors = hasErrors; } /** * 获取所有验证信息 * @return 集合形式 */ public List<ErrorMessage> getAllErrors() { return errors; } /** * 获取所有验证信息 * @return 字符串形式 */ public String getErrors(){ StringBuilder sb = new StringBuilder(); for (ErrorMessage error : errors) { sb.append(error.getPropertyPath()).append(":").append(error.getMessage()).append(" "); } return sb.toString(); } public void addError(String propertyName, String message) { this.errors.add(new ErrorMessage(propertyName, message)); } } @Data public class ErrorMessage { private String propertyPath; private String message; public ErrorMessage() { } public ErrorMessage(String propertyPath, String message) { this.propertyPath = propertyPath; this.message = message; } } }

使用工具类校验参数

java
@Test public void test5() throws IOException { Account account = new Account(); account.setAlias("kalakala"); account.setUserName("wokalakala"); account.setPassWord("密码"); ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(account); if(validResult.hasErrors()){ String errors = validResult.getErrors(); System.out.println(errors); } }

测试结果 passWord:需要匹配正则表达式"[A-Z][a-z][0-9]"

4. 自定义constraint的实现

有时候框架提供的校验注解(constraint)并不能满足我们的需求,所以需要自定义自己的校验规则。满足自己的校验需求。 就拿一个 我自己实现的一个自定义注解为列,我的需求是需要对一个字符串格式的时间进行校验是否满足我期望的字符串格式。 比如我希望的时间格式是

HH:mm
,就需要字符串格式能匹配这个格式。于是自己就实现了一个自定义注解来实现校验。

4.1 编写constraint

注解上必须有 @Constraint(validatedBy = {******.class}) 注解标注,validateBy 的值就是校验逻辑的实现类,实现类必须实现接口ConstraintValidator 自定义注解 必须包含 message ,groups,payload 属性。

java
package com.example.demo.annotation; import org.apache.commons.lang3.time.DateUtils; import javax.validation.Constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.text.ParseException; import java.util.Date; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Created by hu on 2018/3/12. */ @Target({FIELD}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = {DateValidator.DateValidatorInner.class}) public @interface DateValidator { /** * 必须的属性 * 显示 校验信息 * 利用 {} 获取 属性值,参考了官方的message编写方式 *@see org.hibernate.validator 静态资源包里面 message 编写方式 */ String message() default "日期格式不匹配{dateFormat}"; /** * 必须的属性 * 用于分组校验 */ Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; /** * 非必须 */ String dateFormat() default "yyyy-MM-dd HH:mm:ss"; /** * 必须实现 ConstraintValidator接口 */ class DateValidatorInner implements ConstraintValidator<DateValidator, String> { private String dateFormat; @Override public void initialize(DateValidator constraintAnnotation) { this.dateFormat = constraintAnnotation.dateFormat(); } /** * 校验逻辑的实现 * @param value 需要校验的 值 * @return 布尔值结果 */ @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) { return true; } if("".equals(value)){ return true; } try { Date date = DateUtils.parseDate(value, dateFormat); return date != null; } catch (ParseException e) { return false; } } } }

4.2 测试自定义校验注解

在 Account 类的上面添加出生年月日属性,并使用@DateValidator(dateFormat = "yyyy-MM-dd")注解标注。

java
@DateValidator(dateFormat = "yyyy-MM-dd") private String birthday;

测试代码

java
@Test public void test5() throws IOException { Account account = new Account(); account.setAlias("kalakala"); account.setUserName("wokalakala"); account.setPassWord("密码"); account.setBirthday("2001.10.02"); ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(account); if(validResult.hasErrors()){ String errors = validResult.getErrors(); System.out.println(errors); } }

显示结果 显示格式不匹配,自定义校验注解生效啦。 passWord:需要匹配正则表达式"[A-Z][a-z][0-9]" birthday:日期格式不匹配yyyy-MM-dd

5. 分组校验的实现

添加校验注解的方式固然很方便,但是如果Account 对象 在不同的业务中校验规则不同的话,难道我们需要编写两个Account 对象么?答案肯定不是,我们可以使用分组校验,对不同的校验规则进行隔离校验,互相不受影响。

5.1 Account 对象添加多套校验规则

用AccountService 当作一个group ,也可以自己定义空的class对象当作group。 如果不指定groups 那么就是默认的即为Default.class 分组。

java
import javax.validation.constraints.*; import java.util.Date; /** * @date 2018/4/12 11:08 */ @Data @AllArgsConstructor @NoArgsConstructor public class Account { private String id; @NotNull @Length(max = 20) private String userName; @NotNull(groups = {AccountService.class}) @Pattern(regexp = "[A-Z][a-z][0-9]") private String passWord; @DateTimeFormat(pattern = "yyy-MM-dd") private Date createTime; private String alias; @Max(10) @Min(1) private Integer level; private Integer vip; @DateValidator(dateFormat = "yyyy-MM-dd",groups = {AccountService.class}) private String birthday; public Account(String id,String userName,String passWord,Date createTime,String alias,Integer level,Integer vip){ } }

5.2 测试分组校验

测试代码

java
@Test public void test5() throws IOException { Account account = new Account(); account.setAlias("kalakala"); account.setUserName("wokalakala"); account.setPassWord("密码"); account.setBirthday("2001.10.02"); // 指定分组 AccountService.class ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(account, AccountService.class); if(validResult.hasErrors()){ String errors = validResult.getErrors(); System.out.println(errors); } }

测试结果 因为密码的校验规则 是默认分组的所以,这里只显示了生日的校验信息。这样就成功了。 分组校验还可以同时指定多个分组。 birthday:日期格式不匹配yyyy-MM-dd

6. 常用约束规则

规则含义
@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之间
@NotEmpty验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

本文作者:Weee

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!