前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入了解数据校验(Bean Validation):ValidatorFactory和Validator等核心API【享学Java】

深入了解数据校验(Bean Validation):ValidatorFactory和Validator等核心API【享学Java】

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

上篇文章 已经介绍了Bean Validation它的概念、JSR标准,也已经感受了一把使用它来对JavaBean进行校验。本文将继续讲解它的余下执行过程~

在这里先说一句,因为Bean Validation涉及到的API关键类实在是太多了(感叹:hibernate validation实现这一套复杂度非常之高),为此我专门写了一个关键类打点篇,若不熟悉关键组件的,本人强烈建议先花几分钟去浏览一下:深入了解数据校验(Bean Validation):基础类打点(ValidationProvider、ConstraintDescriptor、ConstraintValidator)【享学Java】

上文讲到已经通过配置拿到了ValidatorFactory,本文接着此处继续。

说明:下面的讲解基于此案例:

代码语言:javascript
复制
@Getter
@Setter
@ToString
public class Person {
    // 错误消息message是可以自定义的
    @NotNull(message = "{message} -> 名字不能为null")
    public String name;
    @Max(10)
    @Positive
    public Integer age;

    @NotNull
    @NotEmpty
    private List<@Email String> emails;
    @Future
    private Date start;
}

    public static void main(String[] args) {
        Person person = new Person();
        //person.setName("fsx");
        person.setAge(18);
        // email校验:虽然是List都可以校验哦
        person.setEmails(Arrays.asList("fsx@gmail.com", "baidu@baidu.com", "aaa.com"));
        //person.setStart(new Date()); //start 需要是一个将来的时间: Sun Jul 21 10:45:03 CST 2019
        //person.setStart(new Date(System.currentTimeMillis() + 10000)); //校验通过

        HibernateValidatorConfiguration configure = Validation.byProvider(HibernateValidator.class).configure();
        ValidatorFactory validatorFactory = configure.failFast(false).buildValidatorFactory();
        // 根据validatorFactory拿到一个Validator
        Validator validator = validatorFactory.getValidator();
        // 使用validator对结果进行校验
        Set<ConstraintViolation<Person>> result = validator.validate(person);

        // 对结果进行遍历输出
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);

    }

运行打印:

代码语言:javascript
复制
name {message} -> 名字不能为null -> 名字不能为null: null
age 最大不能超过10: 18
emails[2].<list element> 不是一个合法的电子邮件地址: aaa.com

ValidatorFactory:验证器工厂

在准备好了一个javax.validation.Configuration后,接下来最重要的事就是根据配置Configuration拿到一个ValidatorFactory,进而拿到javax.validation.Validator完成校验~

此类从命名上看非常简单:Validator工厂

代码语言:javascript
复制
public interface ValidatorFactory extends AutoCloseable {

	// 显然,这个接口是最为重要的
	Validator getValidator();
	// 定义一个新的ValidatorContext验证器上下文,并且和Validator关联上
	ValidatorContext usingContext();

	// 下面这些获取  不用过多解释了~
	MessageInterpolator getMessageInterpolator();
	TraversableResolver getTraversableResolver();
	ConstraintValidatorFactory getConstraintValidatorFactory();
	ParameterNameProvider getParameterNameProvider();
	ClockProvider getClockProvider();

	public <T> T unwrap(Class<T> type);
	// 复写AutoCloseable的方法
	@Override
	public void close();

}

看看它的继承树:

它的实现主要有Spring的实现和Hibernate Validation的实现。因为Spring后续还有专题非常详细的描述,因此本文就只关注Hibernate的实现了~

HibernateValidatorFactory

它是Hibernate Validation提供的,继承自标准接口ValidatorFactory,在原基础上进行了扩展:对脚本进行支持,以及支持Duration事件误差~

代码语言:javascript
复制
public interface HibernateValidatorFactory extends ValidatorFactory {
	// 它用于支持脚本。支持使用注解@ScriptAssert等(使用太少了)
	// 关于Java运行脚本,可参阅javax.script.ScriptEngineManager
	@Incubating
	ScriptEvaluatorFactory getScriptEvaluatorFactory();
	
	// 这主要是处理date/time的误差问题~~~
	@Incubating
	Duration getTemporalValidationTolerance();
	// 复写父接口的方法,使用更精确的HibernateValidatorContext验证器上下文
	@Override
	HibernateValidatorContext usingContext();
}

关于唯一实现类:ValidatorFactoryImpl

代码语言:javascript
复制
public class ValidatorFactoryImpl implements HibernateValidatorFactory {
	... // 省略非常多的成员变量
	// 唯一的构造函数,做了非常非常多初始化的事~~~
	public ValidatorFactoryImpl(ConfigurationState configurationState) {
		...
	}

	// 这个或许是最重要的一个方法
	@Override
	public Validator getValidator() {
		return createValidator(
				constraintValidatorManager.getDefaultConstraintValidatorFactory(),
				valueExtractorManager,
				validatorFactoryScopedContext,
				methodValidationConfiguration
		);
	}
	Validator createValidator(ConstraintValidatorFactory constraintValidatorFactory, ValueExtractorManager valueExtractorManager,
			ValidatorFactoryScopedContext validatorFactoryScopedContext, MethodValidationConfiguration methodValidationConfiguration) {
		BeanMetaDataManager beanMetaDataManager = beanMetaDataManagers.computeIfAbsent(
				new BeanMetaDataManagerKey( validatorFactoryScopedContext.getParameterNameProvider(), valueExtractorManager, methodValidationConfiguration ),
				key -> new BeanMetaDataManager(
						constraintHelper,
						executableHelper,
						typeResolutionHelper,
						validatorFactoryScopedContext.getParameterNameProvider(),
						valueExtractorManager,
						validationOrderGenerator,
						buildDataProviders(),
						methodValidationConfiguration
				)
		 );

		return new ValidatorImpl(
				constraintValidatorFactory,
				beanMetaDataManager,
				valueExtractorManager,
				constraintValidatorManager,
				validationOrderGenerator,
				validatorFactoryScopedContext
		);
	}

	@Override
	public MessageInterpolator getMessageInterpolator() {
		return validatorFactoryScopedContext.getMessageInterpolator();
	}
	@Override
	public TraversableResolver getTraversableResolver() {
		return validatorFactoryScopedContext.getTraversableResolver();
	}
	...
	@Override
	public <T> T unwrap(Class<T> type) {
		if ( type.isAssignableFrom( HibernateValidatorFactory.class ) ) {
			return type.cast( this );
		}
		throw LOG.getTypeNotSupportedForUnwrappingException( type );
	}

	@Override
	public HibernateValidatorContext usingContext() {
		return new ValidatorContextImpl( this );
	}
	...
}

此验证器工厂的实现类源码,不可为不复杂(复杂代码本文并没有贴出),我反复看了几遍仍还处于一个准懵逼的状态。

为此我觉得应该关注点,基于打点文章里已经介绍了关键的各个组件,so我不在纠结于实现(创建)细节,只需要着重关注Validator验证器本身了。

说明:Spring对ValidatorFactory的实现稍微简单点,但也不会太容易。因为绝大多数我们在使用Spring,因此在Spring章节此处不会放过~

Validator:验证器

官方的解释简单明了:校验Bean实例~ ,介绍得非常简单但却又是这么回事有木有

到此处,就正式和Bean的校验开始打交道了,也是我们最直接能出效果的一个API,所以说它是最重要的其实也不过为

代码语言:javascript
复制
public interface Validator {

	// 校验作用在此Bean上面的所有约束(所有属性、方法、构造器的所有约束)
	// groups可以指定只使用某个group,默认是Defualt的group嘛~
	<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);

	// 上面太过于粗暴。这里是校验这个Bean上 某个具体的属性~
	<T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups);
	// 这个就更加精确了,具体的属性的具体value值都要校验
	<T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<?>... groups);
	
	// 返回描述Bean约束的描述符对象,此对象和ConstraintDescriptor关联
	// 并且还持有PropertyDescriptor和ConstructorDescriptor等等~
	BeanDescriptor getConstraintsForClass(Class<?> clazz);
	<T> T unwrap(Class<T> type);

	// 返回用于验证方法和构造函数的参数和返回值的协定。
	// 不巧的是:ValidatorImpl实现了Validator, ExecutableValidator这两个接口
	ExecutableValidator forExecutables();
}

// ================关于ExecutableValidator接口================

// 它用于验证 方法 和 构造函数 的 **参数和返回值**。
public interface ExecutableValidator {

	// 验证方法的入参
	<T> Set<ConstraintViolation<T>> validateParameters(T object, Method method,
													   Object[] parameterValues, Class<?>... groups);
	// 验证方法的返回值
	<T> Set<ConstraintViolation<T>> validateReturnValue(T object, Method method,
														Object returnValue, Class<?>... groups);

	// 不解释~~~~~~~~~~~~
	<T> Set<ConstraintViolation<T>> validateConstructorParameters(Constructor<? extends T> constructor,
																  Object[] parameterValues,
																  Class<?>... groups);
	<T> Set<ConstraintViolation<T>> validateConstructorReturnValue(Constructor<? extends T> constructor,
																   T createdObject,
																   Class<?>... groups);
}

它的继承树:

Spring对它的实现非常丰富,但本文还是只看ValidatorImpl

ValidatorImpl

它是Hibernate Validator提供的唯一校验器实现(思想准备:非常复杂)

代码语言:javascript
复制
public class ValidatorImpl implements Validator, ExecutableValidator {
	private static final Collection<Class<?>> DEFAULT_GROUPS = Collections.<Class<?>>singletonList( Default.class );

	// 分组Group校验的顺序问题
	// 若依赖于校验顺序,可用使用@GroupSequence注解来控制Group顺序
	private final transient ValidationOrderGenerator validationOrderGenerator;
	private final ConstraintValidatorFactory constraintValidatorFactory;
	...

	// 唯一构造函数~
	public ValidatorImpl(ConstraintValidatorFactory constraintValidatorFactory, BeanMetaDataManager beanMetaDataManager,
			ValueExtractorManager valueExtractorManager, ConstraintValidatorManager constraintValidatorManager,
			ValidationOrderGenerator validationOrderGenerator, ValidatorFactoryScopedContext validatorFactoryScopedContext) {
		this.constraintValidatorFactory = constraintValidatorFactory;
		this.beanMetaDataManager = beanMetaDataManager;
		this.valueExtractorManager = valueExtractorManager;
		this.constraintValidatorManager = constraintValidatorManager;
		this.validationOrderGenerator = validationOrderGenerator;
		this.validatorScopedContext = new ValidatorScopedContext( validatorFactoryScopedContext );
		this.traversableResolver = validatorFactoryScopedContext.getTraversableResolver();
		this.constraintValidatorInitializationContext = validatorFactoryScopedContext.getConstraintValidatorInitializationContext();
	}


	@Override
	public final <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
		sanityCheckGroups( groups ); // groups里面的内容不能有null

		@SuppressWarnings("unchecked")
		Class<T> rootBeanClass = (Class<T>) object.getClass();
		BeanMetaData<T> rootBeanMetaData = beanMetaDataManager.getBeanMetaData( rootBeanClass );

		// 若没有约束存在,直接返回~
		if ( !rootBeanMetaData.hasConstraints() ) {
			return Collections.emptySet();
		}

		// ValidationContext这个实体类里面的属性极其多~  持有各种组件的引用
		ValidationContext<T> validationContext = getValidationContextBuilder().forValidate( rootBeanMetaData, object );
		// 找到这个分组们的(带有顺序)
		ValidationOrder validationOrder = determineGroupValidationOrder( groups );

		// ValueContext一个实例用于收集所有相关信息,以验证单个类、属性或方法调用。
		ValueContext<?, Object> valueContext = ValueContext.getLocalExecutionContext(validatorScopedContext.getParameterNameProvider(), object,
				validationContext.getRootBeanMetaData(), PathImpl.createRootPath()
		);

		// 此方法传入的信息量非常大~~ 验证上下文、值上下文,验证器
		// 返回的是失败的消息对象:ConstraintViolation  它是被存储在ValidationContext里的~~~~
		return validateInContext( validationContext, valueContext, validationOrder );
	}

	... // 省略validateProperty和validateValue
	// 下面实现`ExecutableValidator`的相关方法  它的四个接口方法无非就是两个方法:validateParameters和validateReturnValue 略

	//	beanMetaDataManager.getBeanMetaData( clazz )还是相对比较重要的
	@Override
	public final BeanDescriptor getConstraintsForClass(Class<?> clazz) {
		return beanMetaDataManager.getBeanMetaData( clazz ).getBeanDescriptor();
	}

	@Override
	public final <T> T unwrap(Class<T> type) {
		if ( type.isAssignableFrom( Validator.class ) ) {
			return type.cast( this );
		}
		throw LOG.getTypeNotSupportedForUnwrappingException( type );
	}

	// 返回自己即可。因为它是可以校验方法入参、返回值等等的
	@Override
	public ExecutableValidator forExecutables() {
		return this;
	}
	... // 省略所有的私有方法们~
}

同样的hibernate实现这套ValidatorImpl校验逻辑不可为不复杂。幸好的是:对于我们web程序员来说,我个人建议只需要掌握到几大主流的组件的使用,就完全够用了(毕竟它有很多能力是给javaFx提供的)。再若不需要关心实现细节,完全面向接口编程我认为也是ok的~~

执行validator.validate(person)方法后,最终拿到的是多个ConstraintViolation,它代表着校验失败的那些讯息,若是Spring框架集成,拿出来做出友好的提示便可达到我们的校验效果~

总结

本文总体还是还是依托实例,再着眼于原理层面的分析,介绍了ValidatorFactoryValidator核心。和上文不一样的是,这两个API应该是我们使用者最应该关注的,所以他们提供的接口方法,希望小伙伴可以稍微留点心,记点忆。

上面也说了,hibernate validation它对数据校验的实现非常非常的复杂,毕竟它提供的能力也是非常强大的(当然弱弱说一句:作者打代码能力也是有待讨论的)。

但是我个人认为,如文章所述掌握到这种程度至少对于web开发者来说是够用了的,甚至我相信大多数小伙伴都感觉内容已经溢出了,所以那就各自领会吧~

Bean Validation核心原理部分到此就先告一段落,像什么扩展它的SPI如:ConstraintMappingContributorResourceBundleLocatorDefaultGroupSequenceProvider等虽然功能强大可扩展,但本系列就不再讨论了~~~

接下来开始面向小伙伴最为关心的:使用、集成、高级定制等,当然还包括在Spring环境下的高度整合扩展使用,请持续关注~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019年07月23日,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • ValidatorFactory:验证器工厂
    • HibernateValidatorFactory
    • Validator:验证器
      • ValidatorImpl
        • 总结
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档