深入了解数据校验(Bean Validation):ValidatorFactory和Validator等核心API【享学Java】(上)

简介: 深入了解数据校验(Bean Validation):ValidatorFactory和Validator等核心API【享学Java】(上)

前言


上篇文章 已经介绍了Bean Validation它的概念、JSR标准,也已经感受了一把使用它来对JavaBean进行校验。本文将继续讲解它的余下执行过程~


在这里先说一句,因为Bean Validation涉及到的API关键类实在是太多了(感叹:hibernate validation实现这一套复杂度非常之高),为此我专门写了一个关键类打点篇,若不熟悉关键组件的,本人强烈建议先花几分钟去浏览一下:深入了解数据校验(Bean Validation):基础类打点(ValidationProvider、ConstraintDescriptor、ConstraintValidator)【享学Java】


上文讲到已经通过配置拿到了ValidatorFactory,本文接着此处继续。

说明:下面的讲解基于此案例:

@Getter
@Setter
@ToString
public class Person {
    // 错误消息message是可以自定义的
    @NotNull(message = "{message} -> 名字不能为null")
    public String name;
    @Max(10)
    @Positive
    public Integer age;
    @NotNull
    @NotEmpty
    private List<@Email String> emails;
    @Future
    private Date start;
}
    public static void main(String[] args) {
        Person person = new Person();
        //person.setName("fsx");
        person.setAge(18);
        // email校验:虽然是List都可以校验哦
        person.setEmails(Arrays.asList("fsx@gmail.com", "baidu@baidu.com", "aaa.com"));
        //person.setStart(new Date()); //start 需要是一个将来的时间: Sun Jul 21 10:45:03 CST 2019
        //person.setStart(new Date(System.currentTimeMillis() + 10000)); //校验通过
        HibernateValidatorConfiguration configure = Validation.byProvider(HibernateValidator.class).configure();
        ValidatorFactory validatorFactory = configure.failFast(false).buildValidatorFactory();
        // 根据validatorFactory拿到一个Validator
        Validator validator = validatorFactory.getValidator();
        // 使用validator对结果进行校验
        Set<ConstraintViolation<Person>> result = validator.validate(person);
        // 对结果进行遍历输出
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }


运行打印:

name {message} -> 名字不能为null -> 名字不能为null: null
age 最大不能超过10: 18
emails[2].<list element> 不是一个合法的电子邮件地址: aaa.com


ValidatorFactory:验证器工厂


在准备好了一个javax.validation.Configuration后,接下来最重要的事就是根据配置Configuration拿到一个ValidatorFactory,进而拿到javax.validation.Validator完成校验~


此类从命名上看非常简单:Validator工厂


public interface ValidatorFactory extends AutoCloseable {
  // 显然,这个接口是最为重要的
  Validator getValidator();
  // 定义一个新的ValidatorContext验证器上下文,并且和Validator关联上
  ValidatorContext usingContext();
  // 下面这些获取  不用过多解释了~
  MessageInterpolator getMessageInterpolator();
  TraversableResolver getTraversableResolver();
  ConstraintValidatorFactory getConstraintValidatorFactory();
  ParameterNameProvider getParameterNameProvider();
  ClockProvider getClockProvider();
  public <T> T unwrap(Class<T> type);
  // 复写AutoCloseable的方法
  @Override
  public void close();
}


看看它的继承树:image.png


它的实现主要有Spring的实现和Hibernate Validation的实现。因为Spring后续还有专题非常详细的描述,因此本文就只关注Hibernate的实现了~


HibernateValidatorFactory


它是Hibernate Validation提供的,继承自标准接口ValidatorFactory,在原基础上进行了扩展:对脚本进行支持,以及支持Duration事件误差~

public interface HibernateValidatorFactory extends ValidatorFactory {
  // 它用于支持脚本。支持使用注解@ScriptAssert等(使用太少了)
  // 关于Java运行脚本,可参阅javax.script.ScriptEngineManager
  @Incubating
  ScriptEvaluatorFactory getScriptEvaluatorFactory();
  // 这主要是处理date/time的误差问题~~~
  @Incubating
  Duration getTemporalValidationTolerance();
  // 复写父接口的方法,使用更精确的HibernateValidatorContext验证器上下文
  @Override
  HibernateValidatorContext usingContext();
}

关于唯一实现类:ValidatorFactoryImpl


public class ValidatorFactoryImpl implements HibernateValidatorFactory {
  ... // 省略非常多的成员变量
  // 唯一的构造函数,做了非常非常多初始化的事~~~
  public ValidatorFactoryImpl(ConfigurationState configurationState) {
    ...
  }
  // 这个或许是最重要的一个方法
  @Override
  public Validator getValidator() {
    return createValidator(
        constraintValidatorManager.getDefaultConstraintValidatorFactory(),
        valueExtractorManager,
        validatorFactoryScopedContext,
        methodValidationConfiguration
    );
  }
  Validator createValidator(ConstraintValidatorFactory constraintValidatorFactory, ValueExtractorManager valueExtractorManager,
      ValidatorFactoryScopedContext validatorFactoryScopedContext, MethodValidationConfiguration methodValidationConfiguration) {
    BeanMetaDataManager beanMetaDataManager = beanMetaDataManagers.computeIfAbsent(
        new BeanMetaDataManagerKey( validatorFactoryScopedContext.getParameterNameProvider(), valueExtractorManager, methodValidationConfiguration ),
        key -> new BeanMetaDataManager(
            constraintHelper,
            executableHelper,
            typeResolutionHelper,
            validatorFactoryScopedContext.getParameterNameProvider(),
            valueExtractorManager,
            validationOrderGenerator,
            buildDataProviders(),
            methodValidationConfiguration
        )
     );
    return new ValidatorImpl(
        constraintValidatorFactory,
        beanMetaDataManager,
        valueExtractorManager,
        constraintValidatorManager,
        validationOrderGenerator,
        validatorFactoryScopedContext
    );
  }
  @Override
  public MessageInterpolator getMessageInterpolator() {
    return validatorFactoryScopedContext.getMessageInterpolator();
  }
  @Override
  public TraversableResolver getTraversableResolver() {
    return validatorFactoryScopedContext.getTraversableResolver();
  }
  ...
  @Override
  public <T> T unwrap(Class<T> type) {
    if ( type.isAssignableFrom( HibernateValidatorFactory.class ) ) {
      return type.cast( this );
    }
    throw LOG.getTypeNotSupportedForUnwrappingException( type );
  }
  @Override
  public HibernateValidatorContext usingContext() {
    return new ValidatorContextImpl( this );
  }
  ...
}


此验证器工厂的实现类源码,不可为不复杂(复杂代码本文并没有贴出),我反复看了几遍仍还处于一个准懵逼的状态。

为此我觉得应该关注点,基于打点文章里已经介绍了关键的各个组件,so我不在纠结于实现(创建)细节,只需要着重关注Validator验证器本身了。


说明:Spring对ValidatorFactory的实现稍微简单点,但也不会太容易。因为绝大多数我们在使用Spring,因此在Spring章节此处不会放过~


Validator:验证器


官方的解释简单明了:校验Bean实例~ ,介绍得非常简单但却又是这么回事有木有

到此处,就正式和Bean的校验开始打交道了,也是我们最直接能出效果的一个API,所以说它是最重要的其实也不过为


public interface Validator {
  // 校验作用在此Bean上面的所有约束(所有属性、方法、构造器的所有约束)
  // groups可以指定只使用某个group,默认是Defualt的group嘛~
  <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
  // 上面太过于粗暴。这里是校验这个Bean上 某个具体的属性~
  <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups);
  // 这个就更加精确了,具体的属性的具体value值都要校验
  <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups);
  // 返回描述Bean约束的描述符对象,此对象和ConstraintDescriptor关联
  // 并且还持有PropertyDescriptor和ConstructorDescriptor等等~
  BeanDescriptor getConstraintsForClass(Class<?> clazz);
  <T> T unwrap(Class<T> type);
  // 返回用于验证方法和构造函数的参数和返回值的协定。
  // 不巧的是:ValidatorImpl实现了Validator, ExecutableValidator这两个接口
  ExecutableValidator forExecutables();
}
// ================关于ExecutableValidator接口================
// 它用于验证 方法 和 构造函数 的 **参数和返回值**。
public interface ExecutableValidator {
  // 验证方法的入参
  <T> Set<ConstraintViolation<T>> validateParameters(T object, Method method,
                             Object[] parameterValues, Class<?>... groups);
  // 验证方法的返回值
  <T> Set<ConstraintViolation<T>> validateReturnValue(T object, Method method,
                            Object returnValue, Class<?>... groups);
  // 不解释~~~~~~~~~~~~
  <T> Set<ConstraintViolation<T>> validateConstructorParameters(Constructor<? extends T> constructor,
                                  Object[] parameterValues,
                                  Class<?>... groups);
  <T> Set<ConstraintViolation<T>> validateConstructorReturnValue(Constructor<? extends T> constructor,
                                   T createdObject,
                                   Class<?>... groups);
}

image.png


Spring对它的实现非常丰富,但本文还是只看ValidatorImpl。

相关文章
|
3月前
|
安全 Java API
告别繁琐编码,拥抱Java 8新特性:Stream API与Optional类助你高效编程,成就卓越开发者!
【8月更文挑战第29天】Java 8为开发者引入了多项新特性,其中Stream API和Optional类尤其值得关注。Stream API对集合操作进行了高级抽象,支持声明式的数据处理,避免了显式循环代码的编写;而Optional类则作为非空值的容器,有效减少了空指针异常的风险。通过几个实战示例,我们展示了如何利用Stream API进行过滤与转换操作,以及如何借助Optional类安全地处理可能为null的数据,从而使代码更加简洁和健壮。
101 0
|
20天前
|
分布式计算 Java 大数据
大数据-147 Apache Kudu 常用 Java API 增删改查
大数据-147 Apache Kudu 常用 Java API 增删改查
24 1
|
2月前
|
安全 Java API
时间日期API(Date,SimpleDateFormat,Calendar)+java8新增日期API (LocalTime,LocalDate,LocalDateTime)
这篇文章介绍了Java中处理日期和时间的API,包括旧的日期API(Date、SimpleDateFormat、Calendar)和Java 8引入的新日期API(LocalTime、LocalDate、LocalDateTime)。文章详细解释了这些类/接口的方法和用途,并通过代码示例展示了如何使用它们。此外,还讨论了新旧API的区别,新API的不可变性和线程安全性,以及它们提供的操作日期时间的灵活性和简洁性。
|
2月前
|
Java 程序员 API
Java 8新特性之Lambda表达式与Stream API的探索
【9月更文挑战第24天】本文将深入浅出地介绍Java 8中的重要新特性——Lambda表达式和Stream API,通过实例解析其语法、用法及背后的设计哲学。我们将一探究竟,看看这些新特性如何让Java代码变得更加简洁、易读且富有表现力,同时提升程序的性能和开发效率。
|
2月前
|
Java
flyway报错Caused by: java.lang.NoSuchMethodError: org.flywaydb.core.api.configuration.FluentConfigurat
flyway报错Caused by: java.lang.NoSuchMethodError: org.flywaydb.core.api.configuration.FluentConfigurat
30 2
|
23天前
|
缓存 前端开发 Java
Java中的RESTful API原则
总结而言,遵循RESTful原则不仅能够提升API的互操作性,还便于维护和扩展,是构建现代Web服务的重要实践。通过精心设计的URI、利用HTTP协议特性以及采用成熟框架如Spring Boot,Java开发者能够高效地创建出既强大又易于使用的RESTful API。
46 0
|
2月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
60 11
|
3月前
|
Java API
Java 8新特性:Lambda表达式与Stream API的深度解析
【7月更文挑战第61天】本文将深入探讨Java 8中的两个重要特性:Lambda表达式和Stream API。我们将首先介绍Lambda表达式的基本概念和语法,然后详细解析Stream API的使用和优势。最后,我们将通过实例代码演示如何结合使用Lambda表达式和Stream API,以提高Java编程的效率和可读性。
|
3月前
|
Java API 开发者
|
3月前
|
存储 算法 Oracle
19 Java8概述(Java8概述+lambda表达式+函数式接口+方法引用+Stream+新时间API)
19 Java8概述(Java8概述+lambda表达式+函数式接口+方法引用+Stream+新时间API)
63 8