hibernate Validator 使用介绍

hibernate Validator 使用介绍

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 校验Demo

hibernate validator(官方文档)提供了一套比较完善、便捷的验证实现方式。

spring-boot-starter-web包里面有hibernate-validator包,不需要引用hibernate validator依赖。

package top.zjzcn.model;

import lombok.Data;

import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

/**
 * @author zhang.junze
 **/
@Data
public class DemoModel1 {
    @NotBlank(message = "用户名不能为空")
    private String userName;

    @NotBlank(message = "年龄不能为空")
    @Pattern(regexp = "^[0-9]{1,2}$", message = "年龄不正确")
    private String age;

    @AssertFalse(message = "必须为false")
    private Boolean isFalse;
    /**
     * 如果是空,则不校验,如果不为空,则校验
     */
    @Pattern(regexp = "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", message = "出生日期格式不正确")
    private String birthday;
}

package top.zjzcn.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import top.zjzcn.model.DemoModel1;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

/**
 * @author zhang.junze
 **/
@RestController
@RequestMapping("validate")
@Slf4j
public class ValidateController {

    //如果成功返回success,不成功返回错误信息
    @GetMapping("/demo1")
    public void testDemo1(@RequestBody @Valid DemoModel1 model1, BindingResult result){
       if(result.hasErrors()){
            for (ObjectError error : result.getAllErrors()) {
                System.out.println(error.getDefaultMessage());
            }
        }
    }
}

POST请求传入的参数{}

[用户名不能为空, 年龄不能为空]

POST请求传入的参数{"userName":"zhangjz","age":120,"isFalse":true,"birthday":"21010-21-12"}

[出生日期格式不正确, 年龄不正确, 必须为false]

4.Hibernate Validator 的校验模式

Hibernate Validator有以下两种验证模式:

  1. 普通模式(默认是这个模式)

普通模式(会校验完所有的属性,然后返回所有的验证失败信息)

  1. 快速失败返回模式

快速失败返回模式(只要有一个验证失败,则返回)

两种验证模式配置方式:(参考官方文档)

failFast:true 快速失败返回模式 false 普通模式

ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .addProperty("hibernate.validator.fail_fast", "false")
                .buildValidatorFactory();
        return factory.getValidator();

和 (hibernate.validator.fail_fast:true 快速失败返回模式 false 普通模式)

ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
               .configure()
               .addProperty("hibernate.validator.fail_fast", "true")
               .buildValidatorFactory();
       return factory.getValidator();

配置hibernate Validator为快速失败返回模式:

package top.zjzcn.config;


import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;


/**
 * @author zhang.junze
 **/
public class ValidatorConfig {

    @Bean
    public Validator validator() {

        ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 将fail_fast设置为true即可,如果想验证全部,则设置为false或者取消配置即可
                .addProperty("hibernate.validator.fail_fast", "true")
                .buildValidatorFactory();
        return factory.getValidator();

    }
}

5.Hibernate Validator简单实践

以下为最简使用方式

  1. 全局异常类
package top.zjzcn.config;

@Slf4j
@RestControllerAdvice
@Order(value = Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionHandler {
    /**
     * 统一处理请求参数校验(实体对象传参)
     *
     * @param e BindException
     * @return FebsResponse
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String validExceptionHandler(BindException e) {
      
        List errorInformation = e.getBindingResult().getAllErrors()
                .stream()
                .map(ObjectError::getDefaultMessage).collect(Collectors.toList());
        return errorInformation.toString();

    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String validExceptionHandler(MethodArgumentNotValidException e) {

        List errorInformation = e.getBindingResult().getAllErrors()
                .stream()
                .map(ObjectError::getDefaultMessage).collect(Collectors.toList());
        return errorInformation.toString();

    }

    /**
     * 统一处理请求参数校验(普通传参)
     *
     * @param e ConstraintViolationException
     * @return FebsResponse
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public String handleConstraintViolationException(ConstraintViolationException e) {

        List errorInformation = e.getConstraintViolations()
                .stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.toList());
        return errorInformation.toString();
    }
}


  1. 校验模式配置类
@Configuration
public class ValidatorConfig {

    @Bean
    public Validator validator() {

        ValidatorFactory factory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 将fail_fast设置为true即可,如果想验证全部,则设置为false或者取消配置即可
                .addProperty("hibernate.validator.fail_fast", "true")
                .buildValidatorFactory();
        return factory.getValidator();

    }
}

  1. Model类
@Data
public class DemoModel1 {
    @NotBlank(message = "用户名不能为空")
    private String userName;

    @NotBlank(message = "年龄不能为空")
    @Pattern(regexp = "^[0-9]{1,2}$", message = "年龄不正确")
    private String age;

    @AssertFalse(message = "必须为false")
    private Boolean isFalse;
    /**
     * 如果是空,则不校验,如果不为空,则校验
     */
    @Pattern(regexp = "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", message = "出生日期格式不正确")
    private String birthday;
}

  1. Controller类
@RestController
@RequestMapping("validate")
@Validated
@Slf4j
public class ValidateController {

    //如果成功返回success,不成功返回错误信息
    @PostMapping("/demo1")
    public String testDemo1(@RequestBody @Valid DemoModel1 model1, BindingResult result){
        if(result.hasErrors()){
            for (ObjectError error : result.getAllErrors()) {
                System.out.println(error.getDefaultMessage());
            }
        }
        return "success";
    }

    @GetMapping()
    public String getName(@NotBlank(message = "不能为空") String name, @Size(min=5,max = 10,message = "有效长度{min}到{max}个字符")String password){
        log.info("进来了==="+name+"=="+password);
        return name+"=="+password;
    }
}

6.自定义Constraint的实现

有时候框架提供的校验注解(constraint)并不能满足我们的需求,所以需要自定义自己的校验规则。满足自己的校验需求。

比如我希望的时间格式是:yyyy-MM-dd HH:mm:ss,就需要字符串格式能匹配这个格式。于是自己就实现了一个自定义注解来实现校验。

6.1 编写constraint

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

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.*;
import java.text.ParseException;
import java.util.Date;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {DateValidator.DateValidatorInner.class})
public @interface DateValidator {
    /**
     * 必须的属性
     * 显示 校验信息
     * 利用 {} 获取 属性值,参考了官方的message编写方式
     *
     * @see org.hibernate.validator 静态资源包里面 message 编写方式
     */
    String message() default "日期格式不匹配{dateFormat}";

    /**
     * 必须的属性
     * 用于分组校验
     */
    Class[] groups() default {};

    Class[] payload() default {};

    /**
     * 非必须
     */
    String dateFormat() default "yyyy-MM-dd HH:mm:ss";

    /**
     * 必须要实现的接口
     */
    class DateValidatorInner implements ConstraintValidator {
        private String dateFormat;

        /**
         * 初始化内容
         * @param dateValidator
         */
        @Override
        public void initialize(DateValidator dateValidator) {
            this.dateFormat = dateValidator.dateFormat();
        }

        /**
         * 校验逻辑的实现
         *
         * @param value 需要校验的 值
         * @return 布尔值结果
         */
        @Override
        public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
            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 测试自定义注解

  • 在类DemoModel1中新增验证属性
import lombok.Data;
import top.zjzcn.common.constraint.DateValidator;

import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

/**
 * @author zhang.junze
 **/
@Data
public class DemoModel1 {
    @NotBlank(message = "{required}")
    private String userName;

    @NotBlank(message = "{required}")
    @Pattern(regexp = "^[0-9]{1,2}$", message = "{age}")
    private String age;

    @AssertFalse(message = "必须为false")
    private Boolean isFalse;
    /**
     * 如果是空,则不校验,如果不为空,则校验
     */
    @DateValidator //此为新增
    @Pattern(regexp = "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", message = "{date}")
    private String birthday;
}
  • 测试代码
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import top.zjzcn.model.DemoModel1;
import top.zjzcn.utils.ValidationUtil;

@SpringBootTest
class HibernateValidatorDemoApplicationTests {

    @Test
    void contextLoads() {
        DemoModel1 demoModel1 = new DemoModel1();
        demoModel1.setUserName("zhangjz");
        demoModel1.setAge("120");
        demoModel1.setIsFalse(true);
        demoModel1.setBirthday("201010-21-12");
        ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(demoModel1);
        if(validResult.hasErrors()){
            String errors = validResult.getErrors();
            System.out.println(errors);
        }
    }

}

  • 测试结果

    age:年龄不正确 isFalse:必须为false birthday:日期格式不正确 birthday:日期格式不匹配yyyy-MM-dd HH:mm:ss 
    

7.分组

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

7.1 DemoModel添加多套校验规则

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

import lombok.Data;
import top.zjzcn.common.constraint.DateValidator;
import top.zjzcn.common.groups.AddGroup;

import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

/**
 * @author zhang.junze
 **/
@Data
public class DemoModel1 {
    @NotBlank(message = "{required}")
    private String userName;

    @NotBlank(message = "{required}")
    @Pattern(regexp = "^[0-9]{1,2}$", message = "{age}",groups = {AddGroup.class})
    private String age;

    @AssertFalse(message = "必须为false",groups = {AddGroup.class})
    private Boolean isFalse;
    /**
     * 如果是空,则不校验,如果不为空,则校验
     */
    @DateValidator
    @Pattern(regexp = "^[0-9]{4}-[0-9]{2}-[0-9]{2}$", message = "{date}")
    private String birthday;
}

7.2 测试分组校验

  • 测试代码
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import top.zjzcn.common.groups.AddGroup;
import top.zjzcn.model.DemoModel1;
import top.zjzcn.utils.ValidationUtil;

@SpringBootTest
class HibernateValidatorDemoApplicationTests {

    @Test
    void contextLoads() {
        DemoModel1 demoModel1 = new DemoModel1();
        demoModel1.setUserName("zhangjz");
        demoModel1.setAge("120");
        demoModel1.setIsFalse(true);
        demoModel1.setBirthday("201010-21-12");
        ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(demoModel1, AddGroup.class);
        if(validResult.hasErrors()){
            String errors = validResult.getErrors();
            System.out.println(errors);
        }
    }

}

  • 测试结果

由于age与isFalse指定分组,所以只会校验该属性

isFalse:必须为false age:年龄不正确 

8. ValidationMessages.properties属性文件使用

Spring boot 表单验证 @Validated 的 message 国际化资源文件默认必须放在resources/ValidationMessages.properties

如果需要更改资源文件地址,需要进行如下配置

@Configuration
public class ValidationConfig {

    @Bean
    public Validator getValidator() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("资源文件所在位置");

        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(messageSource);

        return validator;
    }

}

9.使用封装工具类代码中校验

  • validationUtil 工具类
package top.zjzcn.utils;

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;

/**
 * @author zhang.junze
 * @description 校验工具类
 * @date 2020/3/10
 **/
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  ValidResult validateBean(T t, Class... groups) {
        ValidResult result = new ValidationUtil().new ValidResult();
        Set> violationSet = validator.validate(t, groups);
        boolean hasError = violationSet != null && violationSet.size() > 0;
        result.setHasErrors(hasError);
        if (hasError) {
            for (ConstraintViolation violation : violationSet) {
                result.addError(violation.getPropertyPath().toString(), violation.getMessage());
            }
        }
        return result;
    }

    /**
     * 校验bean的某一个属性
     *
     * @param obj          bean
     * @param propertyName 属性名称
     * @return ValidResult
     */
    public static  ValidResult validateProperty(T obj, String propertyName) {
        ValidResult result = new ValidationUtil().new ValidResult();
        Set> violationSet = validator.validateProperty(obj, propertyName);
        boolean hasError = violationSet != null && violationSet.size() > 0;
        result.setHasErrors(hasError);
        if (hasError) {
            for (ConstraintViolation violation : violationSet) {
                result.addError(propertyName, violation.getMessage());
            }
        }
        return result;
    }

    /**
     * 校验结果类
     */
    @Data
    public class ValidResult {

        /**
         * 是否有错误
         */
        private boolean hasErrors;

        /**
         * 错误信息
         */
        private List errors;

        public ValidResult() {
            this.errors = new ArrayList<>();
        }

        public boolean hasErrors() {
            return hasErrors;
        }

        public void setHasErrors(boolean hasErrors) {
            this.hasErrors = hasErrors;
        }

        /**
         * 获取所有验证信息
         *
         * @return 集合形式
         */
        public List 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;
        }
    }
}

  • 测试代码
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import top.zjzcn.model.DemoModel1;
import top.zjzcn.utils.ValidationUtil;

@SpringBootTest
class HibernateValidatorDemoApplicationTests {

    @Test
    void contextLoads() {
        DemoModel1 demoModel1 = new DemoModel1();
        demoModel1.setUserName("zhangjz");
        demoModel1.setAge("120");
        demoModel1.setIsFalse(true);
        demoModel1.setBirthday("201010-21-12");
        ValidationUtil.ValidResult validResult = ValidationUtil.validateBean(demoModel1);
        if(validResult.hasErrors()){
            String errors = validResult.getErrors();
            System.out.println(errors);
        }
    }

}

10.常用的注解

Bean Validation 中内置的 constraint
@Null 被注释的元素必须为 null
@NotNull 被注释的元素必须不为 null
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Size(max=, min=) 被注释的元素的大小必须在指定的范围内
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式
Hibernate Validator 附加的 constraint
@NotBlank(message =) 验证字符串非null,且长度必须大于0
@Email 被注释的元素必须是电子邮箱地址
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range(min=,max=,message=) 被注释的元素必须在合适的范围内

//大于0.01,不包含0.01
@NotNull
@DecimalMin(value = "0.01", inclusive = false)
private Integer greaterThan;

//大于等于0.01
@NotNull
@DecimalMin(value = "0.01", inclusive = true)
private BigDecimal greatOrEqualThan;

@Length(min = 1, max = 20, message = "message不能为空")
//不能将Length错用成Range
//@Range(min = 1, max = 20, message = "message不能为空")
private String message;

参考:

https://www.cnblogs.com/mr-yang-localhost/p/7812038.html#_label4

https://www.jianshu.com/p/0bfe2318814f

你可能感兴趣的:(hibernate Validator 使用介绍)