深入了解数据校验(Bean Validation):基础类打点(ValidationProvider、ConstraintDescriptor、ConstraintValidator)【享学Java】(下)

简介: 深入了解数据校验(Bean Validation):基础类打点(ValidationProvider、ConstraintDescriptor、ConstraintValidator)【享学Java】(下)

ValidatorContext:验证器上下文


创建Validator的上下文,例如,建立不同的消息插值器或可遍历分解器。

public interface ValidatorContext {
  ValidatorContext messageInterpolator(MessageInterpolator messageInterpolator);
  ValidatorContext traversableResolver(TraversableResolver traversableResolver);
  ValidatorContext constraintValidatorFactory(ConstraintValidatorFactory factory);
  ValidatorContext parameterNameProvider(ParameterNameProvider parameterNameProvider);
  // @since 2.0
  ValidatorContext clockProvider(ClockProvider clockProvider);
  ValidatorContext addValueExtractor(ValueExtractor<?> extractor);
  // 最终的方法
  Validator getValidator();
}


可以看到这个的API和javax.validation.Configuration是何其相似。它的继承数如下:


image.png


HibernateValidatorContext子接口对此进行了增强,具体增强了哪些方法,请参考HibernateValidatorConfiguration,一毛一样的~

关于它的实现类ValidatorContextImpl,此处只介绍一个方法:


public class ValidatorContextImpl implements HibernateValidatorContext {
  // 创建一个Validator
  private final ValidatorFactoryImpl validatorFactory;
  // 它加入了上面所有的内置的值提取器
  private final ValueExtractorManager valueExtractorManager;
  // 拿到一个校验器  使用ValidatorFactory  Validator就是最终对Bean进行校验的东西  
  // 它持有各种上下文,各种插值器、提取器等等
  @Override
  public Validator getValidator() {
    return validatorFactory.createValidator(
        constraintValidatorFactory,
        valueExtractorDescriptors.isEmpty() ? valueExtractorManager : new ValueExtractorManager( valueExtractorManager, valueExtractorDescriptors ),
        validatorFactoryScopedContextBuilder.build(),
        methodValidationConfigurationBuilder.build()
    );
  }
}



从源码中可以看出来,hibernate validation它也是支持JavaFX的校验的~


ConstraintHelper:约束帮助类


关于内置的这些校验注解,我们可以看到注解上面并没有指定:@Constraint(validatedBy = { }),那你就参考此类吧,它把内置的ConstraintValidator都早早就注册上去了~~~


public class ConstraintHelper {
  public ConstraintHelper() {
    // 每个注解对应有多个校验器。(因为每个注解都可能标注在多种类型上嘛~)
    Map<Class<? extends Annotation>, List<ConstraintValidatorDescriptor<?>>> tmpConstraints = new HashMap<>();
    putConstraint( tmpConstraints, AssertFalse.class, AssertFalseValidator.class );
    putConstraint( tmpConstraints, AssertTrue.class, AssertTrueValidator.class );
    ... // 非常非常多的putConstraint() 方法~~~~
  }
}

归纳总结一下,每个校验注解都必备的三个属性如下:


@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface AssertFalse {
  // 这三个属性是所有校验注解必须的,其余的注解根据具体情况自己自定义~~~~~
  String message() default "{javax.validation.constraints.AssertFalse.message}";
  Class<?>[] groups() default { };
  Class<? extends Payload>[] payload() default { };
  // Defines several {@link AssertFalse} annotations on the same 
  @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
  @Retention(RUNTIME)
  @Documented
  @interface List {
    AssertFalse[] value();
  }
}


若自定义注解的话,自己可以随便定义属性,并且实现ConstraintValidator接口实现个校验器书写校验逻辑,写在注解上@Constraint(validatedBy = { })即可生效~~~


ConstraintViolation:约束冲突


描述约束冲突。此对象公开约束冲突上下文以及描述冲突的消息。

public interface ConstraintViolation<T> {
  // 返回已经插值过的错误消息
  String getMessage();
  // 未插值过的原始消息模版
  String getMessageTemplate();
  // 校验的Root Bean(若是校验方法那就是方法所在的Bean)
  T getRootBean();
  Class<T> getRootBeanClass();
  Object getLeafBean();
  Object[] getExecutableParameters();
  Object getExecutableReturnValue();
  // the property path to the value from {@code rootBean}
  Path getPropertyPath();
  // 验证木有通过的值
  Object getInvalidValue();
  // 这个很重要~~~信息量很大
  ConstraintDescriptor<?> getConstraintDescriptor();
  <U> U unwrap(Class<U> type);
}


它的继承树如下


image.png


子接口HibernateConstraintViolation增加了一个方法:


public interface HibernateConstraintViolation<T> extends ConstraintViolation<T> {
  <C> C getDynamicPayload(Class<C> type);
}


关于唯一实现类ConstraintViolationImpl呢,没啥好说的,逻辑都不在它着,在调用它的地方~ 根据ValidationOperation选择~


ConstraintValidatorContext:约束验证上下文


在应用给定的约束验证器(ConstraintValidator)时,提供上下文数据和操作。正所谓每一个约束(注解)都至少对应一个ConstraintValidator嘛~


我敢说,哪怕你是自己在自定义约束验证器,但是你都很少使用这个上下文。但如果使用好了,效果是非常客观的~


public interface ConstraintValidatorContext {
  // 禁用默认的错误时生成`ConstraintViolation`的方式(默认是使用message嘛)
  // 它的作用是:自己根据不同的message、或者不同的属性来生成不同的ConstraintViolation
  void disableDefaultConstraintViolation();
  // 未插值的message:constraintDescriptor.getMessageTemplate()
  String getDefaultConstraintMessageTemplate();
  ClockProvider getClockProvider();
  // 关于ConstraintViolationBuilder此处就不能在展开了,功能大强大  使用起来也太复杂了
  // 它的作用就是根据message模版,来添加和生成一个ConstraintViolation
  // ConstraintViolationBuilder 可以设置各种参数~~~
  ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate);
  <T> T unwrap(Class<T> type);
}


它的继承树如下:image.png


子接口:HibernateConstraintValidatorContext


// 它增强的作用就是能给插值的时候提供更多的参数
public interface HibernateConstraintValidatorContext extends ConstraintValidatorContext {
  // 为约束冲突的message插值提供额外的参数。它能作用于为该约束生成的**所有约束冲突**
  // 它包括默认值以及通过ConstraintViolationBuilder创建出来的所有冲突
  // 要使用不同的变量值**创建多个约束冲突**,可以在对ConstraintViolationBuilder#addConstraintViolation()连续调用之间调用此方法~~~
  HibernateConstraintValidatorContext addMessageParameter(String name, Object value);
  // 基本同上,只是上面处理的是{},这里处理的是${}  el表达式
  HibernateConstraintValidatorContext addExpressionVariable(String name, Object value);
  // 允许设置可以进一步描述冲突的对象。
  HibernateConstraintValidatorContext withDynamicPayload(Object payload);
  // 返回指定ConstraintValidator约束校验器的负载
  // @since 6.0.9
  @Incubating
  <C> C getConstraintValidatorPayload(Class<C> type);
}


关于实现类:ConstraintValidatorContextImpl


public class ConstraintValidatorContextImpl implements HibernateConstraintValidatorContext {
  private final ConstraintDescriptor<?> constraintDescriptor;
  // 显然  默认值是false
  @Override
  public final void disableDefaultConstraintViolation() {
    defaultDisabled = true;
  }
  @Override
  public final String getDefaultConstraintMessageTemplate() {
    return constraintDescriptor.getMessageTemplate();
  }
  @Override
  public <T> T unwrap(Class<T> type) {
    if ( type.isAssignableFrom( HibernateConstraintValidatorContext.class ) ) {
      return type.cast( this );
    }
    throw LOG.getTypeNotSupportedForUnwrappingException( type );
  }
  ...
}

官方给了简单使用示例(供以参考):


    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        HibernateConstraintValidatorContext context = constraintValidatorContext.unwrap(HibernateConstraintValidatorContext.class);
        // 在addConstraintViolation之前把参数放进去,就可以创建出不同的ConstraintViolation了
        // 若不这么做,所有的ConstraintViolation取值都是一样的喽~~~
        context.addMessageParameter("foo", "bar");
        context.buildConstraintViolationWithTemplate("{foo}")
                .addConstraintViolation();
        context.addMessageParameter("foo", "snafu");
        context.buildConstraintViolationWithTemplate("{foo}")
                .addConstraintViolation();
        return false;
    }
相关文章
|
1天前
|
Java 编译器
Java Character 类
4月更文挑战第13天
|
2天前
|
存储 Java
Java基础教程(7)-Java中的面向对象和类
【4月更文挑战第7天】Java是面向对象编程(OOP)语言,强调将事务抽象成对象。面向对象与面向过程的区别在于,前者通过对象间的交互解决问题,后者按步骤顺序执行。类是对象的模板,对象是类的实例。创建类使用`class`关键字,对象通过`new`运算符动态分配内存。方法包括构造函数和一般方法,构造函数用于对象初始化,一般方法处理逻辑。方法可以有0个或多个参数,可变参数用`类型...`定义。`this`关键字用于访问当前对象的属性。
|
6天前
|
Java Shell
Java 21颠覆传统:未命名类与实例Main方法的编码变革
Java 21颠覆传统:未命名类与实例Main方法的编码变革
10 0
|
6天前
|
Java
Java 15 神秘登场:隐藏类解析未知领域
Java 15 神秘登场:隐藏类解析未知领域
10 0
|
8天前
|
安全 Java
append在Java中是哪个类下的方法
append在Java中是哪个类下的方法
21 9
|
8天前
|
JavaScript Java 测试技术
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)
25 0
基于Java的网络类课程思政学习系统的设计与实现(源码+lw+部署文档+讲解等)
|
9天前
|
存储 安全 Java
java多线程之原子操作类
java多线程之原子操作类
|
10天前
|
Java
Java中的多线程实现:使用Thread类与Runnable接口
【4月更文挑战第8天】本文将详细介绍Java中实现多线程的两种方法:使用Thread类和实现Runnable接口。我们将通过实例代码展示如何创建和管理线程,以及如何处理线程同步问题。最后,我们将比较这两种方法的优缺点,以帮助读者在实际开发中选择合适的多线程实现方式。
19 4
|
11天前
|
Java
在Java中,多态性允许不同类的对象对同一消息做出响应
【4月更文挑战第7天】在Java中,多态性允许不同类的对象对同一消息做出响应
17 2
|
16天前
|
Java
Java通过反射获取类调用方法
Java通过反射获取类调用方法
18 0