前言
同样的,本文算是关于数据校验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里可以对应的看到此配置:
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); } }
它的继承树如下:
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},比如${…}