Анализ принципа реализации Spring Validation

задняя часть WeChat Spring Hibernate

В последнее время необходимо выполнить обработку отправки динамических данных, то есть необходимо проанализировать информацию об определении отправленных полей данных, прежде чем можно будет уточнить соответствующие конкретные типы полей, а затем выполнить преобразование типов данных и проверку достоверности полей, а затем отправить базу данных после бизнес-обработки и разработать набор самостоятельно.Логический цикл проверки слишком длинный, поэтому анализируется принцип реализации Spring Validation, и повторно используется валидатор с различными базовыми шаблонами.Здесь процесс анализа принцип Spring Validation будет записан, не вдаваясь в подробности.

Как использовать весеннюю валидацию
  • Когда Spring Bean инициализируется, проверьте, соответствует ли компонент спецификации JSR-303.
    1. Вручную добавьте BeanValidationPostProcessor Bean
    2. Определите правила проверки в классе модели, такие как @Max, @Min, @NotEmpty.
    3. Объявите Bean, полный код выглядит следующим образом:
@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;
}

4. BeanValidationPostProcessor Bean имеет булев атрибут типа afterInitialization, по умолчанию false, если false, то bean проверяется в процессе postProcessBeforeInitialization, в противном случае bean проверяется в процессе postProcessAfterInitialization
5. Эта проверка использует логику Spring BeanPostProcessor, см.Одна из серий Spring Boot: Как быстро ознакомиться со стеком технологий Spring
6. Нижний уровень проверки вызывает метод doValidate, а затем вызывает validator.validate.Проверка по умолчанию — HibernateValidator, пакет validation-api — спецификация JAVA, а спецификация Spring по умолчанию реализована как пакет hibernate-validator. фреймворк без ORM Hibernate

protected void doValidate(Object bean) {
	Assert.state(this.validator != null, "No Validator set");
	Set<ConstraintViolation<Object>> result = this.validator.validate(bean);

7. HibernateValidator по умолчанию вызывает ValidatorFactoryImpl для создания валидатора и расширяет ValidatorFactoryImpl позже.

  • Поддержка спецификации JSR-303 на уровне метода
    1. Вручную добавьте компонент MethodValidationPostProcessor.
    2. Добавьте в класс аннотацию @Validated (также поддерживает пользовательские аннотации, которые передаются при создании Bean-компонента MethodValidationPostProcessor)
    3. Добавьте аннотации проверки к параметрам метода, такие как @Max, @Min, @NotEmpty, @NotNull и т. д., например
@Component
@Validated
public class BeanForMethodValidation {
    public void validate(@NotEmpty String name, @Min(10) int age) {
        System.out.println("validate, name: " + name + ", age: " + age);
    }
}  

4. MethodValidationPostProcessor использует aop внутри для завершения вызова метода.

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. Нижний уровень также вызывает ValidatorFactoryImpl по умолчанию для создания валидатора, и проверка завершается валидатором.

  • Прямое кодирование вызывает логику проверки, например
public class Person {
@NotNull(message = "性别不能为空")
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);

Как и выше, ValidatorFactoryImpl вызывается по умолчанию для создания валидатора, и валидатор выполняет определенную проверку.

  • Использование в параметрах метода контроллера Springvalid或validatedАннотировать параметры для проверки
    1. Ознакомьтесь с процессом вызова запроса Spring
    2. Вы можете видеть, что проверка параметров выполняется в процессе обработки параметров запроса различными резолверами.
    3. Нижний слой единообразно вызывает метод проверки DataBinder.
    4. Роль DataBinder: Биндер, позволяющий задавать значения свойств целевому объекту, включая поддержку валидации и анализа результатов привязки, то есть биндер обрабатывает параметры в виде строк, переданных запросом, и преобразует их в что действительно нужно серверу Тип, связующее обеспечивает поддержку проверки и может хранить результаты проверки
    5. Валидатор DataBinder по умолчанию инициализируется в ConfigurableWebBindingInitializer, а по умолчанию используется OptionalValidatorFactoryBean. Этот компонент наследует LocalValidatorFactoryBean. получить валидатор
На данный момент все подсказки указывают на ValidatorFactoryImpl, следующий анализ этого класса
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->проверить
BeanMetaDataManager, validationContext, valueContext и другое содержимое инкапсулируются в процессе выполнения, и вся контекстная информация используется при проверке, например, все элементы проверки bean-компонента, подлежащего проверке (включая родительский класс и интерфейс), свойство, проверка параметров метода, информация о проверке. , различные классы инструментов, общие для валидатора, унаследованные от ValidatorFactoryScopedContext (такие как обработка сообщения, скрипт и т. д.) и т. д., содержимое более сложное
2. Проверка группы игнорируется и переходит к обработке группы по умолчанию.
3. Продолжайте вызывать метод doValidateConstraint MetaConstraint и переходите к разным деревьям ограничений в соответствии с разными типами аннотаций.

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

4. Игнорируйте, какие из них простые, а какие составные, потому что оба вызывают метод getInitializedConstraintValidator из ConstraintTree. Этот шаг используется для получения валидатора, соответствующего аннотации проверки (например, DecimalMax, NotEmpty и т. д.), и инициализации валидатор. 5.ConstraintHelperКласс поддерживает все встроенные валидаторы и классифицирует их в соответствии с аннотацией проверки (например, DecimalMax) Класс описания валидатора поддерживает общий шаблон валидатора (например, BigDecimal) следующим образом:

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

При получении валидатора конкретного класса бина сначала получить все валидаторы по аннотации, соответствующий метод ConstraintManager.findMatchingValidatorDescriptor, а затем получить единственный валидатор по типу проверяемого объекта
6. Затем инициализируйтеValidator в соответствии с контекстной информацией, а затем вызовите метод isValid валидатора для проверки

Добро пожаловать в мой публичный аккаунт WeChat

68号小喇叭