Recently, we need to submit dynamic data, that is, we need to analyze the field definition information of the submitted data before we can identify the corresponding specific field type, and then do data type conversion and field validity verification, and then submit the database after business processing. It takes too long to develop a set of verification logic by ourselves. Therefore, the implementation principle of Spring Validation is analyzed and its underlying Validator with various patterns is reused. Here, the process of analyzing Spring Validation principle is recorded without going into details

How do I use Spring Validation
  • When Spring Bean initialization calibration Bean is in line with the JSR – 303 specification 1, manually add BeanValidationPostProcessor Bean 2, validation rules defined in the model class, @max, @min, @notempty, @max, @min, @notempty
@Bean
public BeanPostProcessor beanValidationPostProcessor() {
    return new BeanValidationPostProcessor();
}

@Bean
public UserModel getUserModel() {
    UserModel userModel = new UserModel();
    userModel.setUsername(null);
    userModel.setPassword("123");
    return userModel;
}

@Data
class 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;
}
Copy the code

4, BeanValidationPostProcessor Bean within the properties of a Boolean type afterInitialization, default is false, if it is false, In the process of postProcessBeforeInitialization to verify this bean, Otherwise validated in the process of bean postProcessAfterInitialization 5, the calibration using the spring BeanPostProcessor logic, refer to the spring one of the Boot series: How to quickly familiar with Spring technology stack 6, check the bottom call doValidate method, further calls the validator. Validate that the default validator for HibernateValidator, validation – API package for JAVA specification, Spring’s default specification implementation is the Hibernate-Validator package, which is a 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);
Copy the code

7. By default, HibernateValidator calls ValidatorFactoryImpl to generate a Validator

  • Support JSR – 303 specification method level 1, manually add MethodValidationPostProcessor Bean, classes, with 2 @ Validated annotations (also supports custom annotations, Create MethodValidationPostProcessor beans into) 3, plus validation annotations in the method of parameter, such as @ Max, @ Min, @ NotEmpty, @ NotNull etc., such as
@Component
@Validated
public class BeanForMethodValidation {
    public void validate(@NotEmpty String name, @Min(10) int age) {
        System.out.println("validate, name: " + name + ", age: "+ age); }}Copy the code

4, MethodValidationPostProcessor internal use aop to complete the method call

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()); }Copy the code

5, the underlying default call ValidatorFactoryImpl to generate the Validator, validator to complete the verification

  • Directly code the call validation logic, such as
public class Person {
@NotNull(message = "Gender must not 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);Copy the code

By default, ValidatorFactoryImpl is called to generate a Validator. The validator performs the verification

  • Used in the Spring Controller method parameterValid or validatedNote Indicates the parameters to be verified

    1. Familiarize yourself with Spring’s request invocation process

    2. You can see that various resolvers do parameter validation during the process of processing request parameters

    3. The DataBinder’s validate method is called

    4, DataBinder Binder that allows for setting property values onto a target object, Including support for validation and binding result analysis. The Binder processes the string of parameters submitted by the request and converts them to the type that the server really needs. Validations are supported by a binder, which stores validation results

    5, the validator DataBinder default initialization in ConfigurableWebBindingInitializer, default OptionalValidatorFactoryBean, The Bean inherited LocalValidatorFactoryBean LocalValidatorFactoryBean combines ValidatorFactory, custom validation attribute and the validity of the information is used By default, ValidatorFactoryImpl is used to retrieve the Validator

At this point, all the clues point to ValidatorFactoryImpl. Let’s examine the class
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() ) {
		returnCollections.emptySet(); } ValidationOrder validationOrder = determineGroupValidationOrder( groups ); ValueContext<? , Object> valueContext = `ValueContext.getLocalExecutionContext`( validatorScopedContext.getParameterNameProvider(), object, validationContext.getRootBeanMetaData(), PathImpl.createRootPath() );return validateInContext( validationContext, valueContext, validationOrder );
}
Copy the code

1, getValidator->createValidator->ValidatorImpl-> validatamanager Is the context information used in the verification, such as the verification information of all the verification items (including the superclass and interface), property and method parameters of the bean to be verified. From ValidatorFactoryScopedContext inherited the validator universal various tools (such as message processing, script, etc), etc., the content is more complex, group 2 (group) check ignored, Came to the default group processing validateConstraintsForDefaultGroup – > validateConstraintsForSingleDefaultGroupElement – > validateMetaConstraint (note: MetaConstraint’s doValidateConstraint method is used to maintain all the validations for the bean type, its parent class, and its interface. You need to iterate through the MetaConstraint method to call validateconstraint. ConstraintTree is different depending on the annotation type

public static <U extends Annotation> ConstraintTree<U> of(ConstraintDescriptorImpl<U> composingDescriptor, Type validatedValueType) {
	if ( composingDescriptor.getComposingConstraintImpls().isEmpty() ) {
		return new SimpleConstraintTree<>( composingDescriptor, validatedValueType );
	}
	else {
		returnnew ComposingConstraintTree<>( composingDescriptor, validatedValueType ); }}Copy the code

What go simple, specific, which composing whatever, because both are called ConstraintTree ‘getInitializedConstraintValidator’ method, This step is used to get the validator that validates the annotation (e.g. DecimalMax, NotEmpty, etc.) and initialize the validator. 5. ConstraintHelper maintains all builtin validators. A generic template (such as BigDecimal) for the Validator is maintained in the Validator’s description class based on the verification annotation (such as DecimalMax), as follows:

putConstraints( tmpConstraints, DecimalMax.class,  Arrays.asList(
	DecimalMaxValidatorForBigDecimal.class,
	DecimalMaxValidatorForBigInteger.class,
	DecimalMaxValidatorForDouble.class,
	DecimalMaxValidatorForFloat.class,
	DecimalMaxValidatorForLong.class,
	DecimalMaxValidatorForNumber.class,
	DecimalMaxValidatorForCharSequence.class,
	DecimalMaxValidatorForMonetaryAmount.class
) );
Copy the code

In access to specific bean class the validator, first according to the annotation to obtain all the validator, corresponding method is ConstraintManager findMatchingValidatorDescriptor, 6. InitializeValidator based on the context information, and then call the Validator isValid method verification

Welcome to follow my wechat official account