Recently, it is necessary to submit dynamic data, that is, you need to analyze and submit data field definition information before you can clarify the corresponding specific field types, and then do data type conversion and field validity verification. Then, after doing business processing, submit the database, and develop a set of verification logic by yourself, the cycle is too long. Therefore, the implementation principle of Spring Validation was analyzed, and the various underlying Validator were reused. Here, the process of analyzing the Spring Validation principle will be recorded without going into details.
How to use Spring Validation
Check whether the bean complies with JSR-303 specifications when initializing Spring Bean
1. Manually add BeanValidationPostProcessor Bean
2. Define verification rules in model class, such as @Max, @Min, @NotEmpty
3. Declare the bean, the comprehensive code is as follows:
@Beanpublic BeanPostProcessor beanValidationPostProcessor() { return new BeanValidationPostProcessor();}@Beanpublic UserModel getUserModel() { UserModel userModel = new UserModel(); userModel.setUsername(null); userModel.setPassword("123"); return userModel;}@Dataclass UserModel { @NotNull(message = "username can not be null") @Pattern(regexp = "[a-zA-Z0-9_]{5,10}", message = "username is illegal") private String username; @Size(min = 5, max = 10, message = "password's length is illegal") private String password;}4. BeanValidationPostProcessor There is a boolean type property afterInitialization, which is false by default. If it is false, the bean is verified in the postProcessBeforeInitialization process. Otherwise, the bean is verified in the postProcessAfterInitialization process.
5. This verification uses spring's BeanPostProcessor logic
6. Verify that the doValidate method is called, and further call validator.validate. The default validator is HibernateValidator, the validation-api package is JAVA specification, and the default Spring specification is implemented as the hibernate-validator package. This hibernate non-ORM framework Hibernate
protected void doValidate(Object bean) { Assert.state(this.validator != null, "No Validator set"); Set<ConstraintViolation<Object>> result = this.validator.validate(bean);7. HibernateValidator calls ValidatorFactoryImpl by default to generate validator, and then expand ValidatorFactoryImpl
Support method-level JSR-303 specifications
1. Manually add MethodValidationPostProcessor Bean
2. Add @Validated annotation to the class (custom annotation is also supported, and it is passed in when creating a MethodValidationPostProcessor Bean)
3. Add verification annotations to the parameters of the method, such as @Max, @Min, @NotEmpty, @NotNull, etc.
@Component@Validatedpublic class BeanForMethodValidation { public void validate(@NotEmpty String name, @Min(10) int age) { System.out.println("validate, name: " + name + ", age: " + age); }}4. MethodValidationPostProcessor uses aop to complete the call to the method
public void afterPropertiesSet() { Pointcut pointcut = new `AnnotationMatchingPointcut`(this.validatedAnnotationType, true); this.advisor = new `DefaultPointcutAdvisor`(pointcut, createMethodValidationAdvice(this.validator));}protected Advice createMethodValidationAdvice(@Nullable Validator validator) { return (validator != null ? new `MethodValidationInterceptor`(validator) : new MethodValidationInterceptor());}5. The underlying layer also calls ValidatorFactoryImpl by default to generate validator, and the validator completes verification.
Direct encoding call verification logic, such as
public class Person {@NotNull(message = "Gender cannot be empty")private Gender gender;@Min(10)private Integer age;...}ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();Validator validator = validatorFactory.getValidator();Person person = new Person();person.setGender(Gender.Man);validator.validate(person);Same as above, the default call to ValidatorFactoryImpl is called to generate validator, and the validator completes the specific verification.
Use valid or validated annotation to annotate the parameters to be checked in Spring controller method parameters
1. First, familiarize yourself with the request and call process of Spring
2. You can see that parameter verification is done during the process of various resolvers processing request parameters.
3. The underlying layer calls the validate method of DataBinder uniformly
4. The role of DataBinder: Binder that allows for setting property values onto a target object, including support for validation and binding result analysis, that is, binder processes the string-form parameters submitted by request and converts them into the type that the server really needs. The binder provides support for validation and can store verification results.
5. DataBinder's validator is initialized in the ConfigurableWebBindingInitializer by default. OptionalValidatorFactoryBean is used by default. This bean inherits the LocalValidatorFactoryBean. LocalValidatorFactoryBean combines various verification information such as ValidatorFactory, custom verification properties, etc., and uses ValidatorFactoryImpl to obtain the validator by default.
At this point, all the clues point to ValidatorFactoryImpl. The following analysis is the following
public Validator `getValidator`() { return `createValidator`(constraintValidatorManager.getDefaultConstraintValidatorFactory(), valueExtractorManager, validatorFactoryScopedContext, methodValidationConfiguration );}Validator `createValidator`(ConstraintValidatorFactory constraintValidatorFactory, ValueExtractorManager valueExtractorManager, ValidatorFactoryScopedContext validatorFactoryScopedContext, MethodValidationConfiguration methodValidationConfiguration) { BeanMetaDataManager beanMetaDataManager = beanMetaDataManagers.computeIfAbsent( new BeanMetaDataManagerKey( validatorFactoryScopedContext.getParameterNameProvider(), valueExtractorManager, methodValidationConfiguration ), key -> new BeanMetaDataManager( `constraintHelper`, executableHelper, typeResolutionHelper, validatorFactoryScopedContext.getParameterNameProvider(), valueExtractorManager, validationOrderGenerator, buildDataProviders(), methodValidationConfiguration ) ); return `new ValidatorImpl`( constraintValidatorFactory, beanMetaDataManager, valueExtractorManager, constraintValidatorManager, validationOrderGenerator, validatorFactoryScopedContext );}public final <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) { Contracts.assertNotNull( object, MESSAGES.validatedObjectMustNotBeNull() ); sanityCheckGroups( groups ); ValidationContext<T> validationContext = `getValidationContextBuilder().forValidate( object )`; if ( !validationContext.getRootBeanMetaData().hasConstraints() ) { return Collections.emptySet(); } ValidationOrder validationOrder = determineGroupValidationOrder( groups ); ValueContext<?, Object> valueContext = `ValueContext.getLocalExecutionContext`( validatorScopedContext.getParameterNameProvider(), object, validationContext.getRootBeanMetaData(), PathImpl.createRootPath() ); return validateInContext( validationContext, valueContext, validationOrder );}1. getValidator->createValidator->ValidatorImpl->validate
During the execution process, beanMetaDataManager, validationContext, valueContext and other contents are encapsulated, which are context information that will be used during verification, such as all verification items of the bean to be checked (including parent class and interface), property, and method parameter verification information, and various tool classes commonly used by validator (such as processing of message, script, etc.) inherited from ValidatorFactoryScopedContext, etc., and the content is relatively complex.
2. Group verification is ignored, and the default group processing is validateConstraintsForDefaultGroup->validateConstraintsForSingleDefaultGroupElement->validateMetaConstraint(Note: metaConstraints maintains all verifications of the bean type and its parent class and interface, and it is necessary to traverse and call validateMetaConstraint)
3. Continue to call the doValidateConstraint method of MetaConstraint, and follow different ConstraintTree according to different annotation types.
public static <U extends Annotation> ConstraintTree<U> of(ConstraintDescriptorImpl<U> composingDescriptor, Type validatedValueType) { if ( composingDescriptor.getCompositingConstraintImpls().isEmpty() ) { return new SimpleConstraintTree<>( composingDescriptor, validatedValueType ); } else { return new ComposingConstraintTree<>( composingDescriptor, validatedValueType ); }}4. What should I go simple and what should be composing? Because both of them call the 'getInitializedConstraintValidator' method of ConstraintTree. This step is used to obtain the validator corresponding to the annotation (such as DecimalMax, NotEmpty, etc.) and initialize the validator.
5. ConstraintHelper class maintains all builtin validators and classifies them according to the verification annotation (such as DecimalMax). The validator's description class maintains the validator's generic template (such as BigDecimal) as follows:
putConstraints( tmpConstraints, DecimalMax.class, Arrays.asList( DecimalMaxValidatorForBigDecimal.class, DecimalMaxValidatorForBigInteger.class, DecimalMaxValidatorForDouble.class, DecimalMaxValidatorForFloat.class, DecimalMaxValidatorForLong.class, DecimalMaxValidatorForNumber.class, DecimalMaxValidatorForCharSequence.class, DecimalMaxValidatorForMonetaryAmount.class) );
When obtaining the validator of the specific bean class, first obtain all validators according to the annotation. The corresponding method is ConstraintManager.findMatchingValidatorDescriptor, and then obtain the unique validator according to the type of the object being checked.
6. Then initializeValidator based on the context information, and then call the validator's isValid method to verify
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.