Bean Validation原理篇--07
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Bean Validation原理篇--07相关的知识,希望对你有一定的参考价值。
Bean Validation原理篇--07
- 引言
- ValidationProviderResolver:验证提供程序处理器
- ValidationProvider:校验提供器
- ConstraintDescriptor:约束描述符
- MessageInterpolator:message插值器
- TraversableResolver:可移动的处理器
- ConstraintValidatorFactory:校验器工厂
- ParameterNameProvider:参数名提供器
- ClockProvider
- ValueExtractor:值提取器
- ValidatorContext:验证器上下文
- ConstraintHelper:约束帮助类
- ConstraintViolation:约束冲突
- ConstraintValidatorContext:约束验证上下文
- 参考
引言
本系列前传已经大致对Bean Validation进行了介绍,下面开始对其中核心基础类进行介绍。
ValidationProviderResolver:验证提供程序处理器
javax.validation.ValidationProviderResolver
:确定运行时整个环境中可用的ValidationProvider
列表。
怎么确定呢? —> 通过Java提供的SPI服务发现机制来获取可用的ValidationProvider集合。
- 通过
ServiceLoader
去类路径下寻找META-INF/services/javax.validation.spi.ValidationProvider
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
里可以对应的看到此配置:
SPI核心思想就是实现服务自动发现机制,Java自身提供了Bean Validation的标志规范接口,并且规定了通过Java标准SPI方式进行服务发现,这样用户只需要导入相关Bean Validation规范实现即可,因为对于这些实现框架来说,他们肯定按照SPI要求提供好了相应的文件,如下:
这样,用于指定规范标准的组件库通过SPI查找到对应的实现类后,就会实例化对应的实现类,并且将客户发起的调用代理给创建出来的实现类完成即可,这就是服务发现的好处。
动态切换底层实现类,不会影响用户体验,除非用户需要调用具体某个实现类的新特性。
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----标注ValidationProvider规范的唯一实现
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 );
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,比如$…
ParameterMessageInterpolator
资源束消息插值器,不支持el表达式,支持参数值表达式
public class ParameterMessageInterpolator extends AbstractMessageInterpolator
@Override
public String interpolate(Context context, Locale locale, String term)
// 简单的说就是以$打头,就认为是EL表达式 啥都不处理
if ( InterpolationTerm.isElExpression( term ) )
return term;
else
// 核心处理方法是context.getConstraintDescriptor().getAttributes().get( parameter ) 拿到对应的值
ParameterTermResolver parameterTermResolver = new Bean Validation规范篇----03