SpringBoot is a collection of validation frameworks. Common usage is to add annotations to fields. Using validation annotations can simplify the development process by reducing the need to write validation parameters in Controller. The validation framework is huge, and this article is just the tip of the iceberg.
After the request parameters are obtained, there is some validation of the parameters, which of course only happens when parameter validation is enabled.
Validation class demo
public class Tree {
@Min(3)
private Integer id;
private Integer pid;
}
Copy the code
Whether parameter verification is required
ModelAttributeMethodProcessor - > resolveArgument () : / / binding parameters bindRequestParameters (binder, webRequest); validateIfApplicable(binder, parameter); ModelAttributeMethodProcessor - > validateIfApplicable () : / / get the parameter Annotation for (the 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")) { ......Copy the code
As you can see from the code, the key to parameter validation is an annotation starting with @validated or @valid, which is why parameter validation can only take effect if such an annotation is added.
The process of parameter verification
Validate (validationHints) DataBinder->validate(): for (Validator: Validator) GetValidators ()) {// First pass validationHints must be empty, so else 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, errors); SpringValidatorAdapter->validate(): if (this.targetValidator ! = null) { processConstraintViolations(this.targetValidator.validate(target), errors); }Copy the code
Verification lookups and limitations
This. TargetValidator. Validate (target), the target tree object instance is instantiated
ValidatorImpl->validate(): ValidationContext<T> validationContext = getValidationContextBuilder().forValidate( object ); ValidationContext->ValidationContextBuilder()->forValidate(): / / this is a process of structural validation class, pay attention to the code beanMetaDataManager. GetBeanMetaData (rootBeanClass) beanMetaDataManager - > getBeanMetaData () : BeanMetaData<T> beanMetaData = (BeanMetaData<T>) beanMetaDataCache.computeIfAbsent( beanClass, bc -> createBeanMetaData( bc ) ); BeanMetaDataManager->createBeanMetaData(): 1 / / / / -- the first is the ProgrammaticMetaDataProvider class, it is not set / / second AnnotationMetaDataProvider class, 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; } / / retrieve restrictions in the class type return retrieveBeanConfiguration (beanClass); AnnotationMetaDataProvider->retrieveBeanConfiguration(): Set<ConstrainedElement> constrainedElements = getFieldMetaData(beanClass); / / to iterate through all the way to constrainedElements unless static methods. The addAll (getMethodMetaData (beanClass)); / / to iterate through all the constructor 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).getdeclaredanannotations () {// This is a annotations to get the fields, if this is a non-JDK annotations and validation annotations, Will be encapsulated into ConstraintDescriptorImpl class and added to the metaData. MetaData addAll (findConstraintAnnotations (member, the annotation type)); } return metaData;Copy the code
To find whether there is a limit of annotation code from AnnotationMetaDataProvider – > findConstraintAnnotations (), such as @ NotNull, @ NotEmpty, @ Min these are encapsulated in advance became the key of the map collections, Returns true only if the annotation is in the key of the collection. The types of fields handled by these annotations are the ConstraintHelper->ConstraintHelper() constructor and the ConstraintHelper->builtinConstraints variable
GetBeanConfigurationForHierarchy (provider, clazz) was encapsulated BeanConfiguration array of objects, starting from the tree parsing, then its parent class, until the Object class, and its properties are as follows
Important attributes and functions of BeanConfiguration:
- Sources of the source, AnnotationMetaDataProvider fixed for the ANNOTATION
- Bean parsed by beanClass
- ConstrainedElements The parsed elements, including fields, methods, and constructors, are located
AnnotationMetaDataProvider->getFieldMetaData()
If it is resolved, it will be added to the collection regardless of whether it is restricted or not- If it is a method or constructor, it belongs to the ConstrainedExecutable class
- If it is a field, it belongs to the ConstrainedField class
- The set of constraints
- ConstraintTree encapsulates the constraint information
- Descriptor constraint description
- AnnotationDescriptor descriptor to restrict
- The type annotation class
- Attributes Attributes of an annotation
- ConstraintValidatorClasses restricted enumeration
- MatchingConstraintValidatorDescriptors restricted enumeration
- ValidatorClass specifies the restriction class
constraintValidatorClasses
One of the - ValidatedType restricted field class
- ValidationTargets is fixed to ANNOTATED_ELEMENT
- ValidatorClass specifies the restriction class
- AnnotationDescriptor descriptor to restrict
- The value type of the validatedValueType field
- Descriptor constraint description
- Location records information about a field
- ConstraintTree encapsulates the constraint information
- The set of constraints
@Min(3) private Integer id
Encapsulated into the complex information shown below
BeanMetaDataManager->createBeanMetaData(): // --1 for ( MetaDataProvider provider : metaDataProviders ) { for ( BeanConfiguration<? super T> beanConfiguration : getBeanConfigurationForHierarchy( provider, Clazz)) {// Add builds to the build variable with a set builder.add(beanConfiguration); } } return builder.build();Copy the code
The Builder.build () method stores all of the fields and methods of the tree and its parent class into the Set collection aggregatedElements, differentiates methods from fields, and returns encapsulated BeanMetaDataImpl objects.
Important attributes and functions of BeanMetaDataImpl:
- Whether hasConstraints are limited
- AllMetaConstraints all restrictions
- Limits for directMetaConstraints belonging to your own class
- ExecutableMetaDataMap restricts methods to map collections
- UnconstrainedExecutables There is no limit to the method of setting a collection
- PropertyMetaDataMap Field description,map collection
- BeanDescriptor Restriction description
- ConstraintedProerties Restricts the field
- PropertyName Field name
- Type Field type
- ConstraintDescriptors constraintDescriptors constraintDescriptors
Descriptor constraint description
The same
- ConstraintedMethods constraintedMethods
- ConstraintedConstructors A method of limiting construction
- ConstraintedProerties Restricts the field
- ClassHierarchyWithoutInterfaces parsed class
Verify whether it takes effect
ValidatorImpl->validate(): BeanMetaDataImpl is encapsulated as an attribute of ValidationContext and ValueContext. Return validateInContext(validationContext, valueContext, validationOrder); return validateInContext(validationContext, valueContext, validationOrder); ValidatorImpl->validateInContext(): validateConstraintsForCurrentGroup( validationContext, valueContext ); ValidatorImpl->validateConstraintsForCurrentGroup(): validateConstraintsForDefaultGroup( validationContext, valueContext ); ValidatorImpl - > validateConstraintsForDefaultGroup () : / / obtain belongs to this kind of limit Set < MetaConstraint <? >> metaConstraints = hostingBeanMetaData.getDirectMetaConstraints(); validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, metaConstraints, Group.DEFAULT_GROUP ); ValidatorImpl->validateConstraintsForSingleDefaultGroupElement(): // to traverse the metaConstraints, Verify compliance with Boolean TMP = validateMetaConstraint (validationContext, valueContext, valueContext getCurrentBean (), metaConstraint ); ValidatorImpl->validateMetaConstraint(): success = metaConstraint.validateConstraint( validationContext, valueContext ); MetaConstraint->validateConstraint(): success = doValidateConstraint( validationContext, valueContext ); MetaConstraint - > doValidateConstraint () : / / get the type of validation, this is the FIELD valueContext. SetElementType (getElementType ()); boolean validationResult = constraintTree.validateConstraints( executionContext, valueContext ); ConstraintTree->validateConstraints(): // constraintViolations validateConstraints( executionContext, valueContext, constraintViolations ); SimpleConstraintTree->validateConstraints(): returns ConstraintValidator<B,? > validator = getInitializedConstraintValidator( validationContext, valueContext ); . // After getting the validator, Validate constraintViolations. AddAll (validateSingleConstraint (validationContext valueContext, constraintValidatorContext, validator ) ); ConstraintTree->validateSingleConstraint(): / / according to the different validator get different results, if not match will return to limit set isValid = validator. IsValid (validatedValue constraintValidatorContext);Copy the code
Annotations and field types
ConstraintTree->getInitializedConstraintValidator(): validator = getInitializedConstraintValidator( validationContext ); / / field does not match the words will throw an exception if the 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(): / / traverse matchingConstraintValidatorDescriptors, the current field is validatedType / / no returns null, class or subclass When there are multiple return the first List < Type > discoveredSuitableTypes = findSuitableValidatorTypes (validatedValueType, availableValidatorDescriptors.keySet() );Copy the code
As you can see from the code, it is mainly traversalmatchingConstraintValidatorDescriptors
In thevalidatedType
Returns if the field type is equal or a subclass of itvalidatorClass
, such as@Min
Is returned for the Integer typeMinValidatorForNumber
If no matching type returns a default descriptor, an exception similar to type mismatch is thrown when the default descriptor is used. After the descriptor is calledisValid
Method returns true/false. If false, a description of the error restriction set (such as the message, object reference, value, field, or restriction) is created. If no message is displayed, a template is used to read the corresponding file based on the current localeValidationMessages_zh_CN.properties
This file).
The final processing
In ConstraintTree – > validateConstraints () method, if there is an error limit, will be added to the failingConstraintViolations variables, this variable is a collection of the Set, used to store in violation of the restrictions.
Back to ValidatorImpl – > validateConstraintsForSingleDefaultGroupElement (), into the next limit validation.
Back to ValidatorImpl – > validateConstraintsForDefaultGroup (), enter the validation of the parent.
ModelAttributeMethodProcessor->resolveArgument(): / / if there is an error limit, this limit will be added to the bidder. BiddingResult. Errors variable validateIfApplicable (binder, parameter); // If (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); }Copy the code
conclusion
The process is roughly as follows:
- After the parameter binding is complete, check whether parameter verification is enabled
- If so, iterate through the parameter class and its parent class to find which class uses the validation annotation, what is the validation field for that class, and what annotation is used for that validation field
- Iterates through the class, traversing the validation fields and throwing an exception if the annotation does not match the field type; Otherwise, check if it matches, and add it to the collection if it does not
- Check whether the collection is empty. If not, continue the binding and validation of the next parameter. Otherwise, throw an exception