SpringBoot集合了验证框架包,常见用法是在字段上添加注解,使用验证注解可以减少在Controller写参数的验证,从而简化开发流程。验证框架十分庞大,本文所展示的不过是它的冰山一角。
在获得请求参数之后,会有一段参数验证的过程,当然,只有在开启参数验证的时候,才会进行。
验证类demo
public class Tree {
@Min(3)
private Integer id;
private Integer pid;
}
是否需要参数验证
ModelAttributeMethodProcessor->resolveArgument():
// 绑定参数
bindRequestParameters(binder, webRequest);
validateIfApplicable(binder, parameter);
ModelAttributeMethodProcessor->validateIfApplicable():
// 获取参数的注解
for (Annotation ann : parameter.getParameterAnnotations()) {
Object[] validationHints = determineValidationHints(ann);
// 以下是参数验证的过程
if (validationHints != null) {
binder.validate(validationHints);
break;
}
}
ModelAttributeMethodProcessor->determineValidationHints():
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
......
从代码中可以看出,是否需要参数验证的关键是参数中有@Validated或以@Valid开头的注解,这就是为什么只有加上这样的注解参数验证才能生效的原因了。
参数验证的过程
// 接前面的binder.validate(validationHints)
DataBinder->validate():
for (Validator validator : getValidators()) {
// 第一次传过来的时候validationHints必然为空,所以走else if 方法
if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
((SmartValidator) validator).validate(target, bindingResult, validationHints);
}
else if (validator != null) {
validator.validate(target, bindingResult);
}
}
ValidatorAdapter->validate():
// 注意这里的this.target和和target并非同一对象
this.target.validate(target, errors);
SpringValidatorAdapter->validate():
if (this.targetValidator != null) {
processConstraintViolations(this.targetValidator.validate(target), errors);
}
验证的查找与限制
this.targetValidator.validate(target),这里的target是实例化的tree对象实例
ValidatorImpl->validate():
ValidationContext<T> validationContext = getValidationContextBuilder().forValidate( object );
ValidationContext->ValidationContextBuilder()->forValidate():
// 这是构造验证类的过程,关注这句代码beanMetaDataManager.getBeanMetaData( rootBeanClass )
BeanMetaDataManager->getBeanMetaData():
BeanMetaData<T> beanMetaData = (BeanMetaData<T>) beanMetaDataCache.computeIfAbsent( beanClass,
bc -> createBeanMetaData( bc ) );
BeanMetaDataManager->createBeanMetaData(): // --1
// 第一个是ProgrammaticMetaDataProvider类,它没有设置
// 第二个是AnnotationMetaDataProvider类,意为通过注解提供验证
for ( MetaDataProvider provider : metaDataProviders ) {
for ( BeanConfiguration<? super T> beanConfiguration : getBeanConfigurationForHierarchy( provider, clazz ) ) {
builder.add( beanConfiguration );
}
}
BeanMetaDataManager->getBeanConfigurationForHierarchy():
BeanConfiguration<? super T> configuration = provider.getBeanConfiguration( clazz );
AnnotationMetaDataProvider->getBeanConfiguration():
if ( Object.class.equals( beanClass ) ) {
return (BeanConfiguration<T>) objectBeanConfiguration;
}
// 获取类中的限制类型
return retrieveBeanConfiguration( beanClass );
AnnotationMetaDataProvider->retrieveBeanConfiguration():
// 获取字段中的限制类型
Set<ConstrainedElement> constrainedElements = getFieldMetaData( beanClass );
// 遍历所有的方法,去除非静态方法
constrainedElements.addAll( getMethodMetaData( beanClass ) );
// 遍历所有的构造方法
constrainedElements.addAll( getConstructorMetaData( beanClass ) );
AnnotationMetaDataProvider->getFieldMetaData():
// 遍历所有字段,去除非静态字段
propertyMetaData.add( findPropertyMetaData( field ) );
AnnotationMetaDataProvider->findPropertyMetaData():
Set<MetaConstraint<?>> constraints = convertToMetaConstraints(
findConstraints( field, ElementType.FIELD ),
field
);
AnnotationMetaDataProvider->findConstraints():
for ( Annotation annotation : ( (AccessibleObject) member ).getDeclaredAnnotations() ) {
// 接下来是得到字段的注解,如果非jdk注解并且是验证注解的话,则会封装成ConstraintDescriptorImpl类,并添加到metaData中
metaData.addAll( findConstraintAnnotations( member, annotation, type ) );
}
return metaData;
查找是否有限制注解的代码从AnnotationMetaDataProvider->findConstraintAnnotations()开始,如@NotNull、@NotEmpty、@Min这些被提前封装成了map集合的key,只有注解在集合的key中,才返回true,具体有哪些注解,这些注解对应处理的字段类型见ConstraintHelper->ConstraintHelper()构造方法以及ConstraintHelper->builtinConstraints变量
getBeanConfigurationForHierarchy(provider, clazz)得到的是封装的BeanConfiguration对象数组,从tree开始解析,然后是其父类,直到Object类,它的属性如下
BeanConfiguration重要属性与作用:
- source 来源,AnnotationMetaDataProvider固定为ANNOTATION
- beanClass 解析的bean
- constrainedElements 解析的元素,包括字段,方法和构造方法,位于
AnnotationMetaDataProvider->getFieldMetaData(),不管有没有限制,只要解析了,就会添加进集合中- 如果是方法或者构造方法,则属于ConstrainedExecutable类
- 如果是字段,则属于ConstrainedField类
- constraints 限制的set集合
- constraintTree 封装了限制信息
- descriptor 限制描述
- annotationDescriptor 限制的注解描述
- type 注解类
- attributes 注解的属性
- constraintValidatorClasses 限制类枚举
- matchingConstraintValidatorDescriptors 限制类枚举描述
- validatorClass 限制类,对应
constraintValidatorClasses中的一个 - validatedType 限制的字段类
- validationTargets 固定为ANNOTATED_ELEMENT
- validatorClass 限制类,对应
- annotationDescriptor 限制的注解描述
- validatedValueType 字段的值类型
- descriptor 限制描述
- location 记录的是字段的相关信息
- constraintTree 封装了限制信息
- constraints 限制的set集合
@Min(3) private Integer id被封装成了下图中复杂的信息
BeanMetaDataManager->createBeanMetaData(): // --1
for ( MetaDataProvider provider : metaDataProviders ) {
for ( BeanConfiguration<? super T> beanConfiguration : getBeanConfigurationForHierarchy( provider, clazz ) ) {
// 添加进build的builds变量中,builds是一个set集合
builder.add( beanConfiguration );
}
}
return builder.build();
builder.build()方法,将tree及其父类的所有字段和方法存入Set集合aggregatedElements中,区分出方法还是字段,然后返回封装成BeanMetaDataImpl对象。
BeanMetaDataImpl重要属性与作用:
- hasConstraints 是否有限制
- allMetaConstraints 所有限制
- directMetaConstraints 属于自己类的限制
- executableMetaDataMap 限制方法,map集合
- unconstrainedExecutables 没有限制的方法,set集合
- propertyMetaDataMap 字段描述,map集合
- beanDescriptor 限制描述
- constraintedProerties 限制的字段
- propertyName 字段名称
- type 字段类型
- constraintDescriptors 限制描述,这里的结构与前面的
descriptor 限制描述相同
- constraintedMethods 限制的方法
- constraintedConstructors 限制的构造方法
- constraintedProerties 限制的字段
- classHierarchyWithoutInterfaces 解析过的类
验证是否生效
ValidatorImpl->validate():
// 前面是一段查找限制的过程,BeanMetaDataImpl被封装成ValidationContext和ValueContext的一个属性,validationOrder使用默认的排序
// 该方法返回Set的限制集合
return validateInContext( validationContext, valueContext, validationOrder );
ValidatorImpl->validateInContext():
validateConstraintsForCurrentGroup( validationContext, valueContext );
ValidatorImpl->validateConstraintsForCurrentGroup():
validateConstraintsForDefaultGroup( validationContext, valueContext );
ValidatorImpl->validateConstraintsForDefaultGroup():
// 获取属于本类限制
Set<MetaConstraint<?>> metaConstraints = hostingBeanMetaData.getDirectMetaConstraints();
validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, metaConstraints,
Group.DEFAULT_GROUP );
ValidatorImpl->validateConstraintsForSingleDefaultGroupElement():
// 对metaConstraints遍历,验证是否符合
boolean tmp = validateMetaConstraint( validationContext, valueContext, valueContext.getCurrentBean(), metaConstraint );
ValidatorImpl->validateMetaConstraint():
success = metaConstraint.validateConstraint( validationContext, valueContext );
MetaConstraint->validateConstraint():
success = doValidateConstraint( validationContext, valueContext );
MetaConstraint->doValidateConstraint():
// 获取验证的类型,这里是FIELD
valueContext.setElementType( getElementType() );
boolean validationResult = constraintTree.validateConstraints( executionContext, valueContext );
ConstraintTree->validateConstraints():
// constraintViolations
validateConstraints( executionContext, valueContext, constraintViolations );
SimpleConstraintTree->validateConstraints():
// 注解是否作用于该字段类型,如果匹配,返回约束验证器
ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );
......
// 得到validator后,进行验证
constraintViolations.addAll(
validateSingleConstraint(
validationContext,
valueContext,
constraintValidatorContext,
validator
)
);
ConstraintTree->validateSingleConstraint():
// 根据不同的validator得到不同的结果,如果不匹配的话就会返回限制集合
isValid = validator.isValid( validatedValue, constraintValidatorContext );
注解与字段类型
ConstraintTree->getInitializedConstraintValidator():
validator = getInitializedConstraintValidator( validationContext );
// 字段不匹配的话会抛出异常
if ( validator == DUMMY_CONSTRAINT_VALIDATOR ) {
throw getExceptionForNullValidator( validatedValueType, valueContext.getPropertyPath().asString() );
}
return validator;
ConstraintTree->getInitializedConstraintValidator():
ConstraintValidator<A, ?> validator = validationContext.getConstraintValidatorManager().getInitializedValidator(
validatedValueType,
descriptor,
validationContext.getConstraintValidatorFactory(),
validationContext.getConstraintValidatorInitializationContext()
);
ConstraintValidatorManager->getInitializedValidator():
constraintValidator = createAndInitializeValidator( validatedValueType, descriptor, constraintValidatorFactory, initializationContext );
ConstraintValidatorManager->createAndInitializeValidator():
ConstraintValidatorDescriptor<A> validatorDescriptor = findMatchingValidatorDescriptor( descriptor, validatedValueType );
ConstraintValidatorManager->findMatchingValidatorDescriptor():
// 遍历matchingConstraintValidatorDescriptors,当前字段是否是validatedType类或其子类
// 没有返回null,有多个时返回第一个
List<Type> discoveredSuitableTypes = findSuitableValidatorTypes( validatedValueType, availableValidatorDescriptors.keySet() );
从代码中可以看出,主要是遍历matchingConstraintValidatorDescriptors中的validatedType,如果字段类型相等或是其子类,返回validatorClass,如@Min对于Integer类型返回的是MinValidatorForNumber,如果没有符合的类型返回一个默认描述符,当是这个默认描述符的时候,就抛出一个类似于类型不匹配的异常。之后调用描述符的isValid方法返回true/false,如果为false,会创建错误限制集合的描述(如提示信息,对象引用,值,字段,限制信息),如果提示信息没有,则用模板,主要是根据当前语言环境读取对应的文件,获得提示信息(中文读取的是ValidationMessages_zh_CN.properties这个文件)。
最终处理
在ConstraintTree->validateConstraints()方法中,如果有错误限制,则会添加到failingConstraintViolations变量中,该变量是一个Set集合,用来存储违反的限制。
回到ValidatorImpl->validateConstraintsForSingleDefaultGroupElement(),进入下一个限制的验证。
回到ValidatorImpl->validateConstraintsForDefaultGroup(),进入父类的验证。
ModelAttributeMethodProcessor->resolveArgument():
// 如果有错误限制,会将此限制添加到bidder.biddingResult.errors变量中
validateIfApplicable(binder, parameter);
// 从这里看出,所有的参数绑定错误都属于BindException异常
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
总结
流程大致如下:
- 参数绑定完成后,查询是否开启了参数验证
- 如果是,遍历参数类及其父类,找到什么类使用了验证注解,该类的验证字段是什么,该验证字段使用了什么注解
- 遍历类,遍历验证字段,如果注解不符合字段类型,则抛出异常;否则查看是否符合,不符合添加进集合
- 查询集合是否为空,如果不为空则继续下一个参数的绑定、验证,否则抛出异常