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 locatedAnnotationMetaDataProvider->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 classconstraintValidatorClassesOne of the
              • ValidatedType restricted field class
              • ValidationTargets is fixed to ANNOTATED_ELEMENT
          • The value type of the validatedValueType field
        • Location records information about a field

@Min(3) private Integer idEncapsulated 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 constraintDescriptorsDescriptor constraint descriptionThe same
    • ConstraintedMethods constraintedMethods
    • ConstraintedConstructors A method of limiting construction
  • 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 traversalmatchingConstraintValidatorDescriptorsIn thevalidatedTypeReturns if the field type is equal or a subclass of itvalidatorClass, such as@MinIs returned for the Integer typeMinValidatorForNumberIf 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 calledisValidMethod 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.propertiesThis 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:

  1. After the parameter binding is complete, check whether parameter verification is enabled
  2. 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
  3. 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
  4. Check whether the collection is empty. If not, continue the binding and validation of the next parameter. Otherwise, throw an exception