首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入了解数据校验(Bean Validation):基础类打点(ValidationProvider、ConstraintDescriptor、ConstraintValidator)【享学Java】

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

作者头像
YourBatman
发布2019-09-03 16:08:14
3.1K0
发布2019-09-03 16:08:14
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦
前言

同样的,本文算是关于数据校验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},比如${…}

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 ParameterTermResolver();
			return parameterTermResolver.interpolate( context, term );
		}
	}
}

根据代码里可以看出:注解里的属性都是可以使用引用的。如@NotNull执行context.getConstraintDescriptor().getAttributes()如下:

所以你的message里可以使用{groups}、{message}。。。等占位符。比如这样写@NotNull(message = "{message} -> 名字不能为null") 结果为:{message} -> 名字不能为null -> 名字不能为null: null

ResourceBundleMessageInterpolator

它比上更加强大些,支持EL表达式,使用了javax.el.ExpressionFactory来解析它,最终是委托于InterpolationTerm来处理,具体源码此处就省略了。

Hibernate Validation它使用的是ResourceBundleMessageInterpolator来既支持参数,也支持EL表达式~ 当然如果你对默认的提示词语不开心,你可以自定义自己的插值器哦~~

TraversableResolver:可移动的处理器

它的意思从字面是非常不好理解,我用粗暴的语言解释为:确定某个属性是否能被**ValidationProvider**访问~

注意:访问每个属性的时候它都会被调用来判断一下子~

public interface TraversableResolver {

	// 是否是可达的
	boolean isReachable(Object traversableObject,
						Node traversableProperty,
						Class<?> rootBeanType,
						Path pathToTraversableObject,
						ElementType elementType);
	// 是否是可级联的
	boolean isCascadable(Object traversableObject,
						 Node traversableProperty,
						 Class<?> rootBeanType,
						 Path pathToTraversableObject,
						 ElementType elementType);
}

它的继承树如下:

ConstraintValidatorFactory:校验器工厂

校验里很重要的一个处理逻辑地是ConstraintValidato,它就是工厂,可以根据指定的Class类型生产一个实例(其实就是调用了构造函数new出来一个~)

public interface ConstraintValidatorFactory {
	<T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key);
	// 释放方法是提供给Spring容器集成时 .destroyBean(instance);的
	void releaseInstance(ConstraintValidator<?, ?> instance);
}

它的继承树如下:

此处就只讨论ConstraintValidatorFactoryImpl

public class ConstraintValidatorFactoryImpl implements ConstraintValidatorFactory {

	// NewInstance的run方法最终就是执行了这句话:clazz.getConstructor().newInstance()而已
	// 因此最终就是创建了一个key的实例而已~  Spring相关的会把它和Bean容器结合起来
	@Override
	public final <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
		return run( NewInstance.action( key, "ConstraintValidator" ) );
	}
	@Override
	public void releaseInstance(ConstraintValidator<?, ?> instance) {
		// noop
	}

	// 入参是个函数式接口:java.security.PrivilegedAction
	private <T> T run(PrivilegedAction<T> action) {
		return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
	}
}

ParameterNameProvider:参数名提供器

提供方法、构造函数的入参names们。逻辑比较简单~

public interface ParameterNameProvider {
	List<String> getParameterNames(Constructor<?> constructor);
	List<String> getParameterNames(Method method);
}

它的继承树如下:

DefaultParameterNameProvider
public class DefaultParameterNameProvider implements ParameterNameProvider {
	@Override
	public List<String> getParameterNames(Constructor<?> constructor) {
		return doGetParameterNames( constructor );
	}
	@Override
	public List<String> getParameterNames(Method method) {
		return doGetParameterNames( method );
	}

	// Executable是1.8提供的抽象类。两个子类刚好就是:Method和Constructor
	// 因此在Java8后一个方法搞定:executable.getParameters();
	private List<String> doGetParameterNames(Executable executable) {
		Parameter[] parameters = executable.getParameters();
		List<String> parameterNames = new ArrayList<>( parameters.length );
		for ( Parameter parameter : parameters ) {
			parameterNames.add( parameter.getName() );
		}
		return Collections.unmodifiableList( parameterNames );
	}
}
ReflectionParameterNameProvider

@deprecated since 6.0,因为通过反射去获取方法名字已经是默认的了,所以可以计划移除~

If ‘-parameters’ is used at compile time, actual names will be returned. Otherwise, it will be arg0, arg1…

给个使用**DefaultParameterNameProvider**获取参数名们的例子:

    public static void main(String[] args) {
        DefaultParameterNameProvider parameterNameProvider = new DefaultParameterNameProvider();

        // 拿到Person的无参构造和有参构造(@NoArgsConstructor和@AllArgsConstructor)
        Arrays.stream(Person.class.getConstructors()).forEach(c -> {
            List<String> parameterNames = parameterNameProvider.getParameterNames(c);
            System.out.println(parameterNames);
        });

        // Method此处我就省略不写了 
    }

一样的对于Java8而言,编译器’-parameters’ 才会有争取的名称~

它的效果特别像Spring的org.springframework.core.ParameterNameDiscoverer

ClockProvider

这个接口很简单,就是提供一个Clock,给@Past@Future等判断作为参考

public class DefaultClockProvider implements ClockProvider {
	public static final DefaultClockProvider INSTANCE = new DefaultClockProvider();
	// 单例的存在
	private DefaultClockProvider() {
	}
	@Override
	public Clock getClock() {
		return Clock.systemDefaultZone();
	}

}

默认就是服务器当前时间。若你有参考的时间逻辑,自己提供一个ClockProvider实现注册进Configuration即可~

ValueExtractor:值提取器

值提取器,蛮重要的,从**容器内**把值提取处理啊,@since 2.0

public interface ValueExtractor<T> {
	// 从原始值originalValue提取到receiver里
	void extractValues(T originalValue, ValueReceiver receiver);

	// 提供一组方法,用于接收ValueExtractor提取出来的值
	// 必须将该值传递给与原始值类型对应的最佳方法。
	interface ValueReceiver {
		// 接收从对象中提取的值。
		void value(String nodeName, Object object);
		// 接收从未编入索引的可ITerable对象中提取的值,如List、Map、Iterable等
		void iterableValue(String nodeName, Object object);
		// 处理List
		void indexedValue(String nodeName, int i, Object object);
		// 处理Map
		void keyedValue(String nodeName, Object key, Object object);
	}
}

注意:提取值的时候,会执行doValidate完成校验。容易想到,ValueExtractor的实现类就非常之多(所有的实现类都是内建的,非public的):

示例两个如下:

// 这种泛型的写法非常有意思~int @ExtractedValue[]代表int数组  并且还是使用了@ExtractedValue标注的
class IntArrayValueExtractor implements ValueExtractor<int @ExtractedValue[]> {
	static final ValueExtractorDescriptor DESCRIPTOR = new ValueExtractorDescriptor( new IntArrayValueExtractor() );
	private IntArrayValueExtractor() {
	}
	@Override
	public void extractValues(int[] originalValue, ValueReceiver receiver) {
		for ( int i = 0; i < originalValue.length; i++ ) {
			receiver.indexedValue( NodeImpl.ITERABLE_ELEMENT_NODE_NAME, i, originalValue[i] );
		}
	}
}

class IterableValueExtractor implements ValueExtractor<Iterable<@ExtractedValue ?>> {
	...
	@Override
	public void extractValues(Iterable<?> originalValue, ValueReceiver receiver) {
		for ( Object object : originalValue ) {
			receiver.iterableValue( NodeImpl.ITERABLE_ELEMENT_NODE_NAME, object );
		}
	}
}
class OptionalValueExtractor implements ValueExtractor<Optional<@ExtractedValue ?>> {
	...
	@Override
	public void extractValues(Optional<?> originalValue, ValueExtractor.ValueReceiver receiver) {
		receiver.value( null, originalValue.isPresent() ? originalValue.get() : null );
	}
}

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**是何其相似。它的继承数如下:

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);
}

它的继承树如下:

子接口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);
}

它的继承树如下:

子接口: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;
    }
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年07月22日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • ValidationProviderResolver:验证提供程序处理器
  • ValidationProvider:校验提供器
    • HibernateValidator
    • ConstraintDescriptor:约束描述符
    • MessageInterpolator:message插值器
      • AbstractMessageInterpolator
        • ParameterMessageInterpolator
        • ResourceBundleMessageInterpolator
    • TraversableResolver:可移动的处理器
    • ConstraintValidatorFactory:校验器工厂
    • ParameterNameProvider:参数名提供器
      • DefaultParameterNameProvider
        • ReflectionParameterNameProvider
        • ClockProvider
        • ValueExtractor:值提取器
        • ValidatorContext:验证器上下文
          • ConstraintHelper:约束帮助类
          • ConstraintViolation:约束冲突
          • ConstraintValidatorContext:约束验证上下文
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档