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:
- 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 }
- MetaData Provider returns only metadata that is configured directly for a class
- 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:
- When ValidatorFactory.getValidator() gets the validator, it initializes with a new one of its own. The call stack is as follows:
-
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)
- Get all the fields in this class: clazz. getDeclared Field s ()
-
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)
- Get all the methods in this class: clazz. getDeclared Method s ()
- Exclude static method and isSynthetic method
-
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)
- Find all the annotations labeled on this class and convert them to Constraint Descriptor
-
Processing each Constraint Descriptor that has been found eventually converts the Set < MetaConstraint <?> type
1.
- 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**