Bean Validation原理篇--07

Posted 大忽悠爱忽悠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Bean Validation原理篇--07相关的知识,希望对你有一定的参考价值。

Bean Validation原理篇--07


引言

本系列前传已经大致对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

Bean Validation使用篇----05

Bean Validation入门篇----02

Bean Validation起源篇----01

Bean Validation自定义容器验证篇----06

validation-api 原理篇