Deep understanding of the role of data validation @Valid (cascade validation)

Every sentence

There are two big jokes in the NBA: Kobe Bryant is not talented, and James is not skilled.

Relevant Reading

[Xiaojia Java] Deep understanding of data validation: Java Bean Validation 2.0 (JSR303, JSR349, JSR380) Hibernate-Validation 6.x use case
[Home Spring] Let Controller support data validation of tiling parameters (default Spring MVC uses @Valid to validate JavaBean s only)
Spring method level data validation: @Validated + Method Validation PostProcessor elegantly completes data validation action

<center> Interested in Spring, Sweep Code joins wx group: Java Senior Engineer, Architect 3 groups (with two-dimensional code at the end of the article)</center>

Preface

After the basic principles of Bean Validation are concluded, the following is the dry goods that little partners are most concerned about: usage.
If you're going to use Bean Validation data validation, I'm confident that small partners will be able to use it, but most estimates have a premise: the Spring MVC environment. I did a very simple survey. Nearly 99% of people only use data validation at the Controller level of Spring MVC, and almost 90% of them have to use it with @RequestBody to validate JavaBean s.~

If you understand the use of Bean Validation in this way, it's a bit too one-sided. After all, wrapped in Spring, it's hard to know what it really does.
Anyone familiar with my style knows that I will take you through different scenes in every article, and this chapter is no exception. It will let you know something about data validation outside the Spring framework.~

Group Check

In my Pre-Principle article, group validation is not really necessary, because it is really very simple to use. Here is a use case of group validation.

@Getter
@Setter
@ToString
public class Person {
    // Error message can be customized
    @NotNull(message = "{message} -> Name cannot be null", groups = Simple.class)
    public String name;
    @Max(value = 10, groups = Simple.class)
    @Positive(groups = Default.class) // Built-in grouping: default
    public Integer age;

    @NotNull(groups = Complex.class)
    @NotEmpty(groups = Complex.class)
    private List<@Email String> emails;
    @Future(groups = Complex.class)
    private Date start;

    // Define two groups Simple and Complex
    interface Simple {
    }
    interface Complex {

    }
}

Perform group validation:

    public static void main(String[] args) {
        Person person = new Person();
        //person.setName("fsx");
        person.setAge(18);
        // email Check: Although List can be checked
        person.setEmails(Arrays.asList("fsx@gmail.com", "baidu@baidu.com", "aaa.com"));
        //Person.setStart (new Date (); //start needs to be a future time: Sun Jul 21 10:45:03 CST 2019
        //Person. setStart (new Date (System. current Time Millis () + 10000); // Checkout passed

        HibernateValidatorConfiguration configure = Validation.byProvider(HibernateValidator.class).configure();
        ValidatorFactory validatorFactory = configure.failFast(false).buildValidatorFactory();
        // Get a Validator according to the validator Factory
        Validator validator = validatorFactory.getValidator();


        // Grouping Check (Default Group, Simple Group, Complex Group can be treated differently)
        Set<ConstraintViolation<Person>> result = validator.validate(person, Person.Simple.class);
        //Set<ConstraintViolation<Person>> result = validator.validate(person, Person.Complex.class);

        // Ergodic output of results
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);

    }

Run Print:

Maximum age should not exceed 10:18
 Name {message} - > Name cannot be null - > Name cannot be null: null

You can see the effect intuitively. The validation here only executes constraints on the Group of Person.Simple.class.~

Grouping constraints are relatively common in Spring MVC usage scenarios, but it should be noted that javax.validation.Valid does not provide specified grouping, but the org. spring framework. validation. annotation. Validated extension provides the ability to specify grouping directly at the annotation level.

@ Valid annotation

We know that JSR provides an @Valid annotation for use. Before this article, most of the small partners used it in Controller and combined it with @RequestBody, but after this article, you will have a new understanding of it.~

== This annotation is used to validate cascading attributes, method parameters, or method return types. = =
When validating attributes, method parameters, or method return types, constraints defined on the object and its attributes are validated. In addition, this behavior is applied recursively.

::: To understand @Valid, know when to deal with it:::

MetaDataProvider

Metadata Provider: Provider that constrains related metadata (such as constraints, default group sequences, etc.). Its functions and characteristics are as follows:

  1. Based on different metadata: such as xml, annotations. (There's also a programming mapping) These three types. The corresponding enumeration classes are:
public enum ConfigurationSource {
    ANNOTATION( 0 ),
    XML( 1 ),
    API( 2 ); //programmatic API
}
  1. MetaData Provider returns only metadata that is configured directly for a class
  2. It does not deal with metadata merged from superclasses and interfaces (simply saying that @Valid is invalid at interfaces)
public interface MetaDataProvider {

    // Return the ** comment processing option ** to this Provider configuration. Its only implementation class is Annotation Processing Options Impl
    // It can be configured such as: areMemberConstraints Ignored For Are Return Value Constraints Ignored For Are Return Value Constraints Ignored For
    // That is to say, it can be configured to avoid being checked ~ ~ (for green light)
    AnnotationProcessingOptions getAnnotationProcessingOptions();
    // Returns the `Bean Configuration'applied to this bean and returns null if not
    // Bean Configuration holds a reference to Configuration Source~
    <T> BeanConfiguration<? super T> getBeanConfiguration(Class<T> beanClass);
    
}

// Represents a complete constraint-related configuration of a Java type originating from a Configuration Source. Contains metadata at the field, method, class level
// Of course, it also contains metadata on the default group sequence (less used)
public class BeanConfiguration<T> {
    // Enumeration of Three Sources
    private final ConfigurationSource source;
    private final Class<T> beanClass;
    // Constrained Element represents the element to be checked, and you can see that it will have four subclasses as follows:
    // ConstrainedField/ConstrainedType/ConstrainedParameter/ConstrainedExecutable
    
    // Note: Constrained Executable holds a java.lang.reflect.Executable object
    //Its two subclasses are java.lang.reflect.Method and Constructor
    private final Set<ConstrainedElement> constrainedElements;

    private final List<Class<?>> defaultGroupSequence;
    private final DefaultGroupSequenceProvider<? super T> defaultGroupSequenceProvider;
    ... // It doesn't handle any logic on its own. Parameters are passed in through constructors.
}

Its succession tree:

The three implementation classes correspond to the three metadata types described above. This article obviously only needs to focus on annotations: Annotation MetaData Provider

AnnotationMetaDataProvider

This metadata is derived from annotations, and then it is the default configuration source for Hibernate Validation. It handles elements labeled @Valid here~

public class AnnotationMetaDataProvider implements MetaDataProvider {

    private final ConstraintHelper constraintHelper;
    private final TypeResolutionHelper typeResolutionHelper;
    private final AnnotationProcessingOptions annotationProcessingOptions;
    private final ValueExtractorManager valueExtractorManager;

    // This is a very important property that records all the checked Bean information for the current Bean.~~~
    private final BeanConfiguration<Object> objectBeanConfiguration;

    // Unique constructor
    public AnnotationMetaDataProvider(ConstraintHelper constraintHelper,
            TypeResolutionHelper typeResolutionHelper,
            ValueExtractorManager valueExtractorManager,
            AnnotationProcessingOptions annotationProcessingOptions) {
        this.constraintHelper = constraintHelper;
        this.typeResolutionHelper = typeResolutionHelper;
        this.valueExtractorManager = valueExtractorManager;
        this.annotationProcessingOptions = annotationProcessingOptions;

        // By default, it retrieves all Object-related methods: retrieves them and puts them in something I'm rather puzzled about.~~~  
        // Later, I found that everything is for efficiency.
        this.objectBeanConfiguration = retrieveBeanConfiguration( Object.class );
    }

    // Implementing Interface Method
    @Override
    public AnnotationProcessingOptions getAnnotationProcessingOptions() {
        return new AnnotationProcessingOptionsImpl();
    }


    // If your Bean is Object, it returns directly to ~ (Object in most cases)
    @Override
    @SuppressWarnings("unchecked")
    public <T> BeanConfiguration<T> getBeanConfiguration(Class<T> beanClass) {
        if ( Object.class.equals( beanClass ) ) {
            return (BeanConfiguration<T>) objectBeanConfiguration;
        }
        return retrieveBeanConfiguration( beanClass );
    }
}

As you can see above, the core parsing logic is on the private method retrieveBeanConfiguration(). Summarize the two original entries (a constructor and an interface method) that call this method:

  1. When ValidatorFactory.getValidator() gets the validator, it initializes with a new one of its own. The call stack is as follows:

  1. When the Validator.validate() method is invoked, the Bean MetaDataManager. getBeanMetaData (rootBeanClass) traverses all metaData Providers (two by default, no xml mode) at initialization, takes out all BeanConfiguration and gives it to the BeanMetaData Builder, and finally builds a BeanMetaD belonging to this Bean. ATA. A few points for attention are described as follows:

        1. The `ClassHierarchyHelper.getHierarchy'(beanClass) method is called when dealing with `MetaDataProvider', not just this class. After getting the class itself and all the parent classes, they are handed over to `provider. getBeanConfiguration'(clazz) (** that is to say, any class will process the Object class once **)

retrieveBeanConfiguration() Details

To put it plainly, this method retrieves Constrained Element items from beans that need to be checked, such as attributes, methods, constructors, and so on.

    private <T> BeanConfiguration<T> retrieveBeanConfiguration(Class<T> beanClass) {
        // The scope of its search is: clazz. getDeclared Fields (). What does it mean: it collects all fields of this class, including private and so on, but not all fields of the parent class?
        Set<ConstrainedElement> constrainedElements = getFieldMetaData( beanClass );
        constrainedElements.addAll( getMethodMetaData( beanClass ) );
        constrainedElements.addAll( getConstructorMetaData( beanClass ) );

        //TODO GM: currently class level constraints are represented by a PropertyMetaData. This
        //works but seems somewhat unnatural
        // This TODO is interesting: Currently, class-level constraints are represented by Property Metadata. It's possible, but it seems a little unnatural.
        // ReturnValueMetaData,ExecutableMetaData,ParameterMetaData,PropertyMetaData

        // Anyway: Here's where class-level validators are put in (this set is mostly empty)
        Set<MetaConstraint<?>> classLevelConstraints = getClassLevelConstraints( beanClass );
        if (!classLevelConstraints.isEmpty()) {
            ConstrainedType classLevelMetaData = new ConstrainedType(ConfigurationSource.ANNOTATION, beanClass, classLevelConstraints);
            constrainedElements.add(classLevelMetaData);
        }
        
        // Assemble into a Bean Configuration and return
        return new BeanConfiguration<>(ConfigurationSource.ANNOTATION, beanClass,
                constrainedElements, 
                getDefaultGroupSequence( beanClass ),  //All @GroupSequence annotations annotated on this class
                getDefaultGroupSequenceProvider( beanClass ) // All @GroupSequenceProvider annotations annotated on this class
        );
    }

This step extracts the fields, methods, and other items that need to be checked on the bean. Take the Demo Check Person class in the example above, and the final Bean Configuration is as follows: (two)


This is an intuitive conclusion, you can see that only a simple class actually contains a lot of items.

Here's a sentence: There are so many items, but not every one needs to go to validation logic. Because, after all, most items have no constraints, and most ConstrainedElement.getConstraints() are empty.~

Generally speaking, I suggest that we should not just remember the conclusion, because it is easy to forget, so we should go a little deeper to make the memory more profound. Then we will go deep into the following four aspects:

Retrieve Field: getField MetaData (beanClass)
  1. Get all the fields in this class: clazz. getDeclared Field s ()
  2. Pack each Field as a Constrained Element and store it.~~~

        1. Note: This step completes saving the annotations labeled on each `Field'.
    
Retrieve Method: getMethodMetaData (beanClass)
  1. Get all the methods in this class: clazz. getDeclared Method s ()
  2. Exclude static method and isSynthetic method
  3. Convert each Method into a Constrained Executable with ~ (Constrained Executable is also a Constrained Element). In the meantime, it accomplishes the following (both methods and constructors are complex because they contain input and return values):

        1. Find ways to save all notes
        2. Processing input and return values (including automatic judgment of whether the input or return values are used)
Retrieve Constructor: getConstructor MetaData (beanClass)

Processing the Method exactly the same.

Retrieve Type: getClassLevel Constraints (beanClass)
  1. Find all the annotations labeled on this class and convert them to Constraint Descriptor
  2. Processing each Constraint Descriptor that has been found eventually converts the Set < MetaConstraint <?> type

        1. 
  3. Wrap Set <MetaConstraint<?> in a Constrained Type (Constrained Type is a Constrained Element)

== As for cascade validation, it's added here that dealing with Type will deal with cascade validation, and it's still recursive.==
That is the method (courseware @Valid takes effect here):

    // type Explanation: In the following N cases
    // Field is:.getGenericType()// Type of field
    // Method is:.getGenericReturnType()// return value type
    // Constructor:. getDeclaringClass()// Class where the constructor is located

    // Annotated Element: You don't have to have annotations to come in (every field, method, constructor, etc.)
    private CascadingMetaDataBuilder getCascadingMetaData(Type type, AnnotatedElement annotatedElement, Map<TypeVariable<?>, CascadingMetaDataBuilder> containerElementTypesCascadingMetaData) {
        return CascadingMetaDataBuilder.annotatedObject( type, annotatedElement.isAnnotationPresent( Valid.class ), containerElementTypesCascadingMetaData, getGroupConversions( annotatedElement ) );
    }

The most important sentence for us to understand cascading validation here is: annotated Element. is AnnotationPresent (Valid. class). That is to say, if the element is annotated by this annotation, it proves that it needs to be cascaded checked, which is the role of JSR in locating @Valid.~

Spring upgraded it??? Pay attention to Spring's application later~

ConstraintValidator.isValid() caller

We know that each constraint annotation is handled by the ConstraintValidator.isValid() method, where it is called (only):

public abstract class ConstraintTree<A extends Annotation> {
    ...
    protected final <T, V> Set<ConstraintViolation<T>> validateSingleConstraint(ValidationContext<T> executionContext,
            ValueContext<?, ?> valueContext,
            ConstraintValidatorContextImpl constraintValidatorContext,
            ConstraintValidator<A, V> validator) {
        ...
        V validatedValue = (V) valueContext.getCurrentValidatedValue();
        isValid = validator.isValid( validatedValue, constraintValidatorContext );
        ...
        // Obviously, if the check does not pass, the error message will be returned or the empty set will be returned.
        if ( !isValid ) {
            return executionContext.createConstraintViolations(valueContext, constraintValidatorContext);
        }
        return Collections.emptySet();
    }
    ...
}

This method is called when each Group is executed

success = metaConstraint.validateConstraint( validationContext, valueContext );

MetaConstraint is ready when it is retrieved above. Finally, it gets the checkers for each element through ConstrainedElement.getConstraints and continues to call them.

// ConstraintTree<A>
boolean validationResult = constraintTree.validateConstraints( executionContext, valueContext );

so, in the end, it calls isValid, the real way to do things.

== Having said so much, you may still be in the clouds, so let's show it.==

Demo Show

The JavaBean Person is validated with an example above, but you will find that we are all validated Field attributes in the example. In theory, we know that Bean Validation has the ability of checking methods, constructors, parameters and even recursively checking cascade attributes:

Check Attribute Field

slightly

Check Method Input and Return Values

Check Constructor Input and Return Values

Check both input and return values

These are not directly available and need to be checked at run time. Specific use can be referred to: [Home Spring] Let Controller support data validation of tiling parameters (default Spring MVC uses @Valid to validate JavaBean s only)

Cascade Calibration

What is cascade validation? In fact, when there is a cascade object in the member with validation, it must be checked. This is quite common in practical application scenarios, such as joining Person objects and holding Child objects. We not only need to complete the verification of Person, but also need to check the attributes in Child:

@Getter
@Setter
@ToString
public class Person {

    @NotNull
    private String name;
    @NotNull
    @Positive
    private Integer age;
    @Valid
    @NotNull
    private InnerChild child;

    @Getter
    @Setter
    @ToString
    public static class InnerChild {
        @NotNull
        private String name;
        @NotNull
        @Positive
        private Integer age;
    }

}

The verification logic is as follows:

    public static void main(String[] args) {
        Person person = new Person();
        person.setName("fsx");
        Person.InnerChild child = new Person.InnerChild();
        child.setName("fsx-son");
        child.setAge(-1);
        person.setChild(child); // Put it in

        Validator validator = Validation.byProvider(HibernateValidator.class).configure().failFast(false)
                .buildValidatorFactory().getValidator();
        Set<ConstraintViolation<Person>> result = validator.validate(person);

        // Output error message
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }

Function:

child.age must be positive: -1
 age cannot be null: null

Successful verification of child.age cascade attribute~

summary

This article is worth a deep understanding of Bean Validation. It has not been difficult to use basic data validation, especially in Spring environment.~

Knowledge exchange

If the format of the article is confused, click: Text Link-Text Link-Text Link-Text Link-Text Link-Text Link

== The last: If you think this is helpful to you, you might as well give a compliment. Of course, sharing your circle of friends so that more small partners can see it is also authorized by the author himself.~==

** If you are interested in technical content, you can join the wx group: Java Senior Engineer and Architect.
If the group two-dimensional code fails, Please add wx number: fsx641385712 (or scan the wx two-dimensional code below). And note: "java into the group" will be manually invited to join the group**

Keywords: Java Spring Bean Validation xml

Added by Im Jake on Wed, 31 Jul 2019 18:48:02 +0300