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

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

前言


同样的,本文算是关于数据校验Bean Validation这块的先行文章,对一些关键的基础类进行打点,为了更加顺畅的理解后面具体的文章内容,建议可以把此文当做一个伴手的工具收藏着~


ValidationProviderResolver:验证提供程序处理器


javax.validation.ValidationProviderResolver:确定运行时整个环境中可用的ValidationProvider列表。


系统中ValidationProvider的提供由META-INF/services/javax.validation.spi.ValidationProvider这个配置文件来表示。这个熟悉吗???是不是非常眼熟?对,它就是Java内置的SPI机制~


如果不知道Java的SPI机制,请必看此文:【小家Spring】探讨注解驱动Spring应用的机制,详解ServiceLoader、SpringFactoriesLoader的使用(以JDBC、spring.factories为例介绍SPI)

public interface ValidationProviderResolver {
  // 返回所有可用的ValidationProvider
  List<ValidationProvider<?>> getValidationProviders();
}


它的唯一实现类是javax.validation.Validation的内部类:DefaultValidationProviderResolver


public class Validation {
  private static class DefaultValidationProviderResolver implements ValidationProviderResolver {
    @Override
    public List<ValidationProvider<?>> getValidationProviders() {
      return GetValidationProviderListAction.getValidationProviderList();
    }
  }
  // 调用的工具方法里,最为核心的是这一句代码,其余的就不用多说了
  ServiceLoader<ValidationProvider> loader = ServiceLoader.load( ValidationProvider.class, classloader );
  ...
}


可以看到它的核心就是对Java SPI的利用。我们导入的hibernate validation这个Jar里可以对应的看到此配置:


image.png


ValidationProvider:校验提供器


简单的理解就是提供校验程序的。它提供javax.validation.Configuration以及能够根据配置创出一个ValidatorFactory


public interface ValidationProvider<T extends Configuration<T>> {
  // 这两个方法都是通过引导器BootstrapState 创建一个Configuration
  T createSpecializedConfiguration(BootstrapState state);
  Configuration<?> createGenericConfiguration(BootstrapState state);
  // 根据引导器,得到一个ValidatorFactory 
  // 请注意:Configuration也有同名方法:buildValidatorFactory()
  // Configuration的实现最终调用的是这个方法:getProvider().buildValidatorFactory(this)
  ValidatorFactory buildValidatorFactory(ConfigurationState configurationState);
}


它只有Hibernate对它提供了唯一实现类:HibernateValidator。


Configuration委托ValidationProvider.buildValidatorFactory()得到一个ValidatorFactory,从而最终就能得到Validator啦~


HibernateValidator

public class HibernateValidator implements ValidationProvider<HibernateValidatorConfiguration> {
  // 此处直接new ConfigurationImpl()  他是Hibernate校验的配置类
  // 请注意此两者的区别:一个传的是this,一个传的是入参state~~~
  @Override
  public HibernateValidatorConfiguration createSpecializedConfiguration(BootstrapState state) {
    return HibernateValidatorConfiguration.class.cast( new ConfigurationImpl( this ) );
  }
  @Override
  public Configuration<?> createGenericConfiguration(BootstrapState state) {
    return new ConfigurationImpl( state );
  }
  // ValidatorFactoryImpl是个ValidatorFactory ,也是最为重要的一个类之一
  @Override
  public ValidatorFactory buildValidatorFactory(ConfigurationState configurationState) {
    return new ValidatorFactoryImpl( configurationState );
  }
}


HibernateValidator的代码不多,但它是ValidationProvider的唯一实现


ConstraintDescriptor:约束描述符


描述单个约束或者组合(composing)约束,这个描述非常的重要,到这里就把约束的注解、Groups、Payloads、getAttributes等等都关联起来了,它就是个metadata


public interface ConstraintDescriptor<T extends Annotation> {
  // 返回此约束注解。  如果是组合约束(注解上面标注解),本注解的属性值覆盖组合进来的
  T getAnnotation();
  // 返回原本的message(还没有插值呢)
  String getMessageTemplate();
  // 获得该注解所属的分组  默认都属于javax.validation.groups.Default这个分组
  Set<Class<?>> getGroups();
  // 该约束持有的负载Payload。  Payload是个标记接口,木有任何方法  子接口有Unwrap和Skip接口
  Set<Class<? extends Payload>> getPayload();
  // 标注该约束作用在什么地方(入参or返回值???)  因为约束几乎可以标注在任何位 置,并且还可以标注在TYPE_USE上
  // TYPE_USE:java8新增的ElementType  可以写在字段上、类上面上。。。
  //ConstraintTarget注解取值如下:
  //IMPLICIT:自动判断
      // 如果既不在方法上也不在构造函数上,则表示已注释的元素(类/字段)
      // 如果在没有参数的方法或构造函数上,那就作用在返回值上
      // 如果在没有返回值的方法上,那就作用在入参上
  // RETURN_VALUE:作用在方法/构造函数的返回值上
  // PARAMETERS:作用在方法/构造函数的入参上
  ConstraintTarget getValidationAppliesTo();
  // 得到需要作用在此约束上的所有校验器ConstraintValidator。(因为可能是组合的嘛  所以出现多个校验器是正常现象)
  List<Class<? extends ConstraintValidator<T, ?>>> getConstraintValidatorClasses();
  // 就是此注解的属性-值的Map。包括那三大基础属性
  Map<String, Object> getAttributes();
  // 返回所遇的约束描述们~~~(毕竟可以标注多个注解  组合租借等等)
  Set<ConstraintDescriptor<?>> getComposingConstraints();
  // 如果约束注解上标注有@ReportAsSingleViolation  此处就有返回值
  // 此注解作用:如果任何组合注解失败,承载此注解的约束注解将**返回组合注解错误报告**。
  // 它会忽略每个单独注解写的错误报告message~~~~**合成约束的计算将在第一个验证错误时停止**,也就是它有短路的效果
  boolean isReportAsSingleViolation();
  // @since 2.0 ValidateUnwrappedValue用于特定约束的展开行为(和ValueExtractor提取容器内的值有关)
  // DEFAULT:默认行为
  // UNWRAP:该值在校验前展开,既校验作用于容器内的值
  // SKIP:校验前不展开。相当于直接作用于本元素。比如作用在List上,而非List里面的元素
  ValidateUnwrappedValue getValueUnwrapping();
  <U> U unwrap(Class<U> type);
}


此类对于理解数据校验这块还是非常重要的,它是个metadata,它所在的包为:javax.validation.metadata。它的唯一实现类是:ConstraintDescriptorImpl,关于此实现类,下面只描述些关键的:


public class ConstraintDescriptorImpl<T extends Annotation> implements ConstraintDescriptor<T>, Serializable {
  // 这些注解是会被忽略的,就是去注解上的注解时忽略这些注解
  private static final List<String> NON_COMPOSING_CONSTRAINT_ANNOTATIONS = Arrays.asList(
      Documented.class.getName(),
      Retention.class.getName(),
      Target.class.getName(),
      Constraint.class.getName(),
      ReportAsSingleViolation.class.getName(),
      Repeatable.class.getName(),
      Deprecated.class.getName()
  );
  // 该约束定义的ElementType~~~标注在字段上、方法上、构造器上?
  private final ElementType elementType;
  ...
  // 校验源~。注解定义在实际根类或类层次结构中的某个地方定义~
  // DEFINED_LOCALLY:约束定义在根类
  // DEFINED_IN_HIERARCHY:约束定义在父类、接口处等
  private final ConstraintOrigin definedOn;
  // 当前约束的类型
  // GENERIC:非**交叉参数**约束
  // CROSS_PARAMETER:交叉参数约束
  private final ConstraintType constraintType;
  // 上面已解释
  private final ConstraintTarget validationAppliesTo;
  // 多个约束的联合类型
  // OR:或者关系
  // AND:并且关系
  // ALL_FALSE:相当于必须所有条件都是false才行
  private final CompositionType compositionType;
  private final int hashCode;
  // 几乎所有的准备逻辑都在这个唯一的构造函数里
  public ConstraintDescriptorImpl(ConstraintHelper constraintHelper, Member member, ConstraintAnnotationDescriptor<T> annotationDescriptor,
      ElementType type, Class<?> implicitGroup, ConstraintOrigin definedOn, ConstraintType externalConstraintType) {
    this.annotationDescriptor = annotationDescriptor;
    this.elementType = type;
    this.definedOn = definedOn;
    // 约束上是否标注了@ReportAsSingleViolation注解~
    this.isReportAsSingleInvalidConstraint = annotationDescriptor.getType().isAnnotationPresent(ReportAsSingleViolation.class);
    // annotationDescriptor.getGroups()拿到所属分组
    this.groups = buildGroupSet( annotationDescriptor, implicitGroup );
    // 拿到负载annotationDescriptor.getPayload()
    this.payloads = buildPayloadSet( annotationDescriptor );
    // 对负载payloads类型进行区分  是否要提取值呢??
    this.valueUnwrapping = determineValueUnwrapping( this.payloads, member, annotationDescriptor.getType() );
    // annotationDescriptor.getValidationAppliesTo()
    // 也就是说你自己自定义注解的时候,可以定义一个属性validationAppliesTo = ConstraintTarget.calss 哦~~~~
    this.validationAppliesTo = determineValidationAppliesTo( annotationDescriptor );
    // 委托constraintHelper帮助拿到此注解类型下所有的校验器们
    this.constraintValidatorClasses = constraintHelper.getAllValidatorDescriptors( annotationDescriptor.getType() )
        .stream()
        .map( ConstraintValidatorDescriptor::getValidatorClass )
        .collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );
    // ValidationTarget配合注解`@SupportedValidationTarget(ValidationTarget.ANNOTATED_ELEMENT)`使用
    List<ConstraintValidatorDescriptor<T>> crossParameterValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(
        annotationDescriptor.getType(),
        ValidationTarget.PARAMETERS
    ) );
    List<ConstraintValidatorDescriptor<T>> genericValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(
        annotationDescriptor.getType(),
        ValidationTarget.ANNOTATED_ELEMENT
    ) );
    if ( crossParameterValidatorDescriptors.size() > 1 ) {
      throw LOG.getMultipleCrossParameterValidatorClassesException( annotationDescriptor.getType() );
    }
    // 判定是交叉参数约束  还是非交叉参数约束(这个决策非常的复杂)
    this.constraintType = determineConstraintType(
        annotationDescriptor.getType(), member, type,
        !genericValidatorDescriptors.isEmpty(),
        !crossParameterValidatorDescriptors.isEmpty(),
        externalConstraintType
    );
    // 这个方法比较复杂:解析出作用在此的约束们
    // member:比如Filed字段age  此处:annotationDescriptor.getType()为注解@Positive
    // type.getDeclaredAnnotations() 其实是上面会忽略掉的注解了。当然还可能有@SupportedValidationTarget等等@NotNull等注解
    this.composingConstraints = parseComposingConstraints( constraintHelper, member, constraintType );
    this.compositionType = parseCompositionType( constraintHelper );
    validateComposingConstraintTypes();
    if ( constraintType == ConstraintType.GENERIC ) {
      this.matchingConstraintValidatorDescriptors = CollectionHelper.toImmutableList( genericValidatorDescriptors );
    } else {
      this.matchingConstraintValidatorDescriptors = CollectionHelper.toImmutableList( crossParameterValidatorDescriptors );
    }
    this.hashCode = annotationDescriptor.hashCode();
  }
  ...
}


该类的处理整体上还是非常复杂的,case非常之多,其余private方法此处就忽略了~


MessageInterpolator:message插值器


插值器不好理解,简单的说就是对message内容进行格式化,若有占位符{}或者el表达式就执行替换和计算。对于语法错误应该尽量的宽容。


public interface MessageInterpolator {
  // 根据约束验证上下文格式化消息模板。(Locale对国际化提供了支持~)
  String interpolate(String messageTemplate, Context context);
  String interpolate(String messageTemplate, Context context,  Locale locale);
  // 与插值上下文相关的信息。
  interface Context {
    // ConstraintDescriptor对应于正在验证的约束,整体上进行了描述  上面已说明
    ConstraintDescriptor<?> getConstraintDescriptor();
    // 正在被校验的值
    Object getValidatedValue();
    // 返回允许访问特定于提供程序的API的指定类型的实例。如果bean验证提供程序实现不支持指定的类
    <T> T unwrap(Class<T> type);
  }
}


它的继承树如下:


image.png

AbstractMessageInterpolator

public abstract class AbstractMessageInterpolator implements MessageInterpolator {
  private static final int DEFAULT_INITIAL_CAPACITY = 100;
  private static final float DEFAULT_LOAD_FACTOR = 0.75f;
  private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
  // 默认的国际化资源名称,支持多国语言,请参见下面截图
  private static final String DEFAULT_VALIDATION_MESSAGES = "org.hibernate.validator.ValidationMessages";
  // 规范中定义的用户提供的消息束的名称。
  public static final String USER_VALIDATION_MESSAGES = "ValidationMessages";
  // 由约束定义贡献者定义的消息束的默认名称。
  public static final String CONTRIBUTOR_VALIDATION_MESSAGES = "ContributorValidationMessages";
  // 当前JVM默认的Locale
  private final Locale defaultLocale;
  // 用户指定的国际资源文件  默认的  贡献者贡献的资源文件
  private final ResourceBundleLocator userResourceBundleLocator;
  private final ResourceBundleLocator defaultResourceBundleLocator;
  private final ResourceBundleLocator contributorResourceBundleLocator;
  // 这个Map缓存了1-3步插补文字
  private final ConcurrentReferenceHashMap<LocalizedMessage, String> resolvedMessages;
  // 步骤4
  private final ConcurrentReferenceHashMap<String, List<Token>> tokenizedParameterMessages;
  // 步骤5(El表达式~)
  private final ConcurrentReferenceHashMap<String, List<Token>> tokenizedELMessages;
  public AbstractMessageInterpolator(ResourceBundleLocator userResourceBundleLocator, ResourceBundleLocator contributorResourceBundleLocator, boolean cacheMessages) {
    defaultLocale = Locale.getDefault(); // 默认的Locale
    // 用户自定义的定位器
    if ( userResourceBundleLocator == null ) {
      this.userResourceBundleLocator = new PlatformResourceBundleLocator( USER_VALIDATION_MESSAGES );
    } else {
      this.userResourceBundleLocator = userResourceBundleLocator;
    }
    ... // 其它Map的初始化
  }
  @Override
  public String interpolate(String message, Context context) {
    return interpolateMessage( message, context, defaultLocale);
  }
  // 此处就开始处理message消息了。比如本文可能的消息是:
  // name字段->名字不能为null  (若是自定义)
  // age字段->{javax.validation.constraints.Positive.message}
  private String interpolateMessage(String message, Context context, Locale locale) throws MessageDescriptorFormatException {
    // if the message does not contain any message parameter, we can ignore the next steps and just return
    // the unescaped message. It avoids storing the message in the cache and a cache lookup.
    if ( message.indexOf( '{' ) < 0 ) {
      return replaceEscapedLiterals( message );
    }
    String resolvedMessage = null;
    // either retrieve message from cache, or if message is not yet there or caching is disabled,
    // perform message resolution algorithm (step 1)
    if ( cachingEnabled ) {
      resolvedMessage = resolvedMessages.computeIfAbsent( new LocalizedMessage( message, locale ), lm -> resolveMessage( message, locale ) );
    } else {
      // 结合国际化资源文件处理~~
      resolvedMessage = resolveMessage( message, locale );
    }
    // 2-3步骤:若字符串里含有{param} / ${expr}这种 就进来解析
    //  给占位符插值依赖于这个抽象方法public abstract String interpolate(Context context, Locale locale, String term);
    // 解析EL表达式也是依赖于这个方法~
    // 最后:处理转义字符
    ...
    return resolvedMessage;
  }
  ...
  //抽象方法,给你context,给你locale  给你term(字符串),你帮我把这个字符串给我处理了
  public abstract String interpolate(Context context, Locale locale, String term);
}


该抽象类完成了绝大部分工作,留下来的工作不多了:interpolate插值方法。(抽象类本身是提供了**{}和el的支持**的,就看子类的插值方法喽~)

需要注意的是:这个teim是待处理的串不是完整的,比如{value},比如${…}






相关文章
|
9天前
|
Java Linux
java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
24 1
|
9天前
|
Java 索引
java基础(13)String类
本文介绍了Java中String类的多种操作方法,包括字符串拼接、获取长度、去除空格、替换、截取、分割、比较和查找字符等。
22 0
java基础(13)String类
|
24天前
|
Java
java的类详解
在 Java 中,类是面向对象编程的核心概念,用于定义具有相似特性和行为的对象模板。以下是类的关键特性:唯一且遵循命名规则的类名;描述对象状态的私有属性;描述对象行为的方法,包括实例方法和静态方法;用于初始化对象的构造方法;通过封装保护内部属性;通过继承扩展其他类的功能;以及通过多态增强代码灵活性。下面是一个简单的 `Person` 类示例,展示了属性、构造方法、getter 和 setter 方法及行为方法的使用。
|
20天前
|
Java 编译器
Java——类与对象(继承和多态)
本文介绍了面向对象编程中的继承概念,包括如何避免重复代码、构造方法的调用规则、成员变量的访问以及权限修饰符的使用。文中详细解释了继承与组合的区别,并探讨了多态的概念,包括向上转型、向下转型和方法的重写。此外,还讨论了静态绑定和动态绑定的区别,以及多态带来的优势和弊端。
22 9
Java——类与对象(继承和多态)
|
20天前
|
SQL Java 编译器
Java——类与对象(封装)
封装是面向对象编程中的概念,指将数据(属性)和相关操作(方法)组合成独立单元(类),使外部无法直接访问对象的内部状态,只能通过提供的方法进行交互,从而保护数据安全。例如,手机将各种组件封装起来,只暴露必要的接口供外部使用。实现封装时,使用`private`关键字修饰成员变量,并提供`get`和`set`方法进行访问和修改。此外,介绍了包的概念、导入包的方式及其注意事项,以及`static`关键字的使用,包括静态变量和方法的初始化与代码块的加载顺序。
24 10
Java——类与对象(封装)
|
2天前
|
Java API
Java的日期类都是怎么用的
【10月更文挑战第1天】本文介绍了 Java 中处理日期和时间的三个主要类:`java.util.Date`、`java.util.Calendar` 和 `java.time` 包下的新 API。`Date` 类用于表示精确到毫秒的瞬间,可通过时间戳创建或获取当前日期;`Calendar` 抽象类提供丰富的日期操作方法,如获取年月日及时区转换;`java.time` 包中的 `LocalDate`、`LocalTime`、`LocalDateTime` 和 `ZonedDateTime` 等类则提供了更为现代和灵活的日期时间处理方式,支持时区和复杂的时间计算。
26 14
|
20天前
|
Java C语言
Java——类与对象
这段内容介绍了Java中的类和对象、`this`关键字及构造方法的基本概念。类是对现实世界事物的抽象描述,包含属性和方法;对象是类的实例,通过`new`关键字创建。`this`关键字用于区分成员变量和局部变量,构造方法用于初始化对象。此外,还介绍了标准JavaBean的要求和生成方法。
21 9
Java——类与对象
|
19天前
|
存储 安全 Java
Java——String类详解
String 是 Java 中的一个类,用于表示字符串,属于引用数据类型。字符串可以通过多种方式定义,如直接赋值、创建对象、传入 char 或 byte 类型数组。直接赋值会将字符串存储在串池中,复用相同的字符串以节省内存。String 类提供了丰富的方法,如比较(equals() 和 compareTo())、查找(charAt() 和 indexOf())、转换(valueOf() 和 format())、拆分(split())和截取(substring())。此外,还介绍了 StringBuilder 和 StringJoiner 类,前者用于高效拼接字符串,后者用于按指定格式拼接字符串
19 1
Java——String类详解
|
10天前
|
存储 Java
Java编程中的对象和类
【8月更文挑战第55天】在Java的世界中,“对象”与“类”是构建一切的基础。就像乐高积木一样,类定义了形状和结构,而对象则是根据这些设计拼装出来的具体作品。本篇文章将通过一个简单的例子,展示如何从零开始创建一个类,并利用它来制作我们的第一个Java对象。准备好让你的编程之旅起飞了吗?让我们一起来探索这个神奇的过程!
25 10
|
6天前
|
安全 Java 编译器
java访问类字段
java访问类字段
下一篇
无影云桌面