【源码解析】SpringBoot接口参数校验原理

使用示例

入门

web接口

@RestController
public class HelloController {

    @PostMapping("/t1")
    public void t1(@Validated @RequestBody Request request) {
        System.out.println(11);
    }
}

实体类

@Data
public class Request {

    @NotEmpty(message = "title不为空")
    private String title;

    @NotEmpty(message = "name不为空")
    private String name;
}

访问接口,会校验messagename的数据

自定义的校验器

注解

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy ={StatusValidator.class})
public @interface Status {
 
    String[] statusType() default {};
 
    String message() default "状态传递有误";
 
    Class<?>[] groups() default {};
 
    Class<? extends Payload>[] payload() default {};
 
}

StatusValidator自定义的校验器

public class StatusValidator implements ConstraintValidator<Status, Integer> {

    private List<String> typeStatus;

    @Override
    public void initialize(Status constraintAnnotation) {
        typeStatus = Arrays.asList(constraintAnnotation.statusType());
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
        if (value != null) {
            if (!typeStatus.contains(String.valueOf(value))) {
                return false;
            }
        }
        return true;
    }
}

在实体类上添加校验属性

@Data
public class Request {

    @NotEmpty(message = "title不为空")
    private String title;

    @NotEmpty(message = "name不为空")
    private String name;

    @Status(statusType = {"1","2"})
    private Integer loginStatus;
}

非web接口校验

@Service
@Validated
public class HelloService {

    public void sayHello(@Valid Request request) {
        System.out.println(request);
    }
}

嵌套校验

实体类Request,包含嵌套校验的属性User

@Data
public class Request {

    @NotEmpty(message = "title不为空")
    private String title;

    @NotEmpty(message = "name不为空")
    private String name;

    @Status(statusType = {"1","2"})
    private Integer loginStatus;

    @Valid
    @NotNull
    private User user;

}

实体类

@Data
public class User {

    @NotEmpty(message = "密码不能为空")
    private String password;

    @Max(value = 1)
    private Integer age;
}

分组校验

校验属性设置分组

@Data
public class Request {

    @NotEmpty(message = "title不为空", groups = Group1.class)
    private String title;

    @NotEmpty(message = "name不为空", groups = Group2.class)
    private String name;

    @Status(statusType = {"1","2"})
    private Integer loginStatus;

    private User user;


    public interface Group1 {
    }

    public interface Group2 {
    }
}


对参数进行分组校验

@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @PostMapping("/t1")
    public void t1(@Validated(Request.Group1.class) @RequestBody Request request) {
        System.out.println(11);
    }
}

快速失败

@Configuration
public class ValidationConfig {
    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 开启 fail fast 机制
                .failFast(true)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }
}
  • failFast设置为true表示开启快速失败,即当一个规则校验不通过时,就不在校验其他的规则了。

  • failFast设置为false表示不开启快速失败,会校验所有的规则。

标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理 标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理 标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理

源码解析

web参数校验

RequestResponseBodyMethodProcessor#resolveArgument,解析接口中的参数。判断校验结果是否有错,抛出异常。MethodArgumentNotValidException属于BindException的一种。

	@Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
			if (arg != null) {
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
				}
			}
			if (mavContainer != null) {
				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
			}
		}

		return adaptArgumentIfNecessary(arg, parameter);
	}

AbstractMessageConverterMethodArgumentResolver#validateIfApplicable,进行校验

	protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
		Annotation[] annotations = parameter.getParameterAnnotations();
		for (Annotation ann : annotations) {
			Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
			if (validationHints != null) {
				binder.validate(validationHints);
				break;
			}
		}
	}

ValidationAnnotationUtils#determineValidationHints,判断是否存在注解,ValidValidated

	public static Object[] determineValidationHints(Annotation ann) {
		Class<? extends Annotation> annotationType = ann.annotationType();
		String annotationName = annotationType.getName();
		if ("javax.validation.Valid".equals(annotationName)) {
			return EMPTY_OBJECT_ARRAY;
		}
		Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
		if (validatedAnn != null) {
			Object hints = validatedAnn.value();
			return convertValidationHints(hints);
		}
		if (annotationType.getSimpleName().startsWith("Valid")) {
			Object hints = AnnotationUtils.getValue(ann);
			return convertValidationHints(hints);
		}
		return null;
	}

	private static Object[] convertValidationHints(@Nullable Object hints) {
		if (hints == null) {
			return EMPTY_OBJECT_ARRAY;
		}
		return (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
	}

DataBinder#validate(java.lang.Object...),遍历校验器。

	public void validate(Object... validationHints) {
		Object target = getTarget();
		Assert.state(target != null, "No target to validate");
		BindingResult bindingResult = getBindingResult();
		// Call each validator with the same binding result
		for (Validator validator : getValidators()) {
			if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
				((SmartValidator) validator).validate(target, bindingResult, validationHints);
			}
			else if (validator != null) {
				validator.validate(target, bindingResult);
			}
		}
	}

ValidatorImpl#validate,对实体类进行校验

    public final <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
        Contracts.assertNotNull(object, Messages.MESSAGES.validatedObjectMustNotBeNull());
        this.sanityCheckGroups(groups);
        Class<T> rootBeanClass = object.getClass();
        BeanMetaData<T> rootBeanMetaData = this.beanMetaDataManager.getBeanMetaData(rootBeanClass);
        if (!rootBeanMetaData.hasConstraints()) {
            return Collections.emptySet();
        } else {
            BaseBeanValidationContext<T> validationContext = this.getValidationContextBuilder().forValidate(rootBeanClass, rootBeanMetaData, object);
            ValidationOrder validationOrder = this.determineGroupValidationOrder(groups);
            BeanValueContext<?, Object> valueContext = ValueContexts.getLocalExecutionContextForBean(this.validatorScopedContext.getParameterNameProvider(), object, validationContext.getRootBeanMetaData(), PathImpl.createRootPath());
            return this.validateInContext(validationContext, valueContext, validationOrder);
        }
    }

ValidatorImpl#validateConstraintsForDefaultGroup,根据组级别获取MetaConstraint

	private <U> void validateConstraintsForDefaultGroup(BaseBeanValidationContext<?> validationContext, BeanValueContext<U, Object> valueContext) {
		final BeanMetaData<U> beanMetaData = valueContext.getCurrentBeanMetaData();
		final Map<Class<?>, Class<?>> validatedInterfaces = new HashMap<>();

		// evaluating the constraints of a bean per class in hierarchy, this is necessary to detect potential default group re-definitions
		for ( Class<? super U> clazz : beanMetaData.getClassHierarchy() ) {
			BeanMetaData<? super U> hostingBeanMetaData = beanMetaDataManager.getBeanMetaData( clazz );
			boolean defaultGroupSequenceIsRedefined = hostingBeanMetaData.isDefaultGroupSequenceRedefined();

			// if the current class redefined the default group sequence, this sequence has to be applied to all the class hierarchy.
			if ( defaultGroupSequenceIsRedefined ) {
				Iterator<Sequence> defaultGroupSequence = hostingBeanMetaData.getDefaultValidationSequence( valueContext.getCurrentBean() );
				Set<MetaConstraint<?>> metaConstraints = hostingBeanMetaData.getMetaConstraints();

				while ( defaultGroupSequence.hasNext() ) {
					for ( GroupWithInheritance groupOfGroups : defaultGroupSequence.next() ) {
						boolean validationSuccessful = true;

						for ( Group defaultSequenceMember : groupOfGroups ) {
							validationSuccessful = validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz,
									metaConstraints, defaultSequenceMember ) && validationSuccessful;
						}

						validationContext.markCurrentBeanAsProcessed( valueContext );

						if ( !validationSuccessful ) {
							break;
						}
					}
				}
			}
			// fast path in case the default group sequence hasn't been redefined
			else {
				Set<MetaConstraint<?>> metaConstraints = hostingBeanMetaData.getDirectMetaConstraints();
				validateConstraintsForSingleDefaultGroupElement( validationContext, valueContext, validatedInterfaces, clazz, metaConstraints,
						Group.DEFAULT_GROUP );
				validationContext.markCurrentBeanAsProcessed( valueContext );
			}

			// all constraints in the hierarchy has been validated, stop validation.
			if ( defaultGroupSequenceIsRedefined ) {
				break;
			}
		}
	}

ValidatorImpl#validateConstraintsForSingleDefaultGroupElement,对于每一个MetaConstraint执行校验

	private <U> boolean validateConstraintsForSingleDefaultGroupElement(BaseBeanValidationContext<?> validationContext, ValueContext<U, Object> valueContext, final Map<Class<?>, Class<?>> validatedInterfaces,
			Class<? super U> clazz, Set<MetaConstraint<?>> metaConstraints, Group defaultSequenceMember) {
		boolean validationSuccessful = true;

		valueContext.setCurrentGroup( defaultSequenceMember.getDefiningClass() );

		for ( MetaConstraint<?> metaConstraint : metaConstraints ) {
			// HV-466, an interface implemented more than one time in the hierarchy has to be validated only one
			// time. An interface can define more than one constraint, we have to check the class we are validating.
			final Class<?> declaringClass = metaConstraint.getLocation().getDeclaringClass();
			if ( declaringClass.isInterface() ) {
				Class<?> validatedForClass = validatedInterfaces.get( declaringClass );
				if ( validatedForClass != null && !validatedForClass.equals( clazz ) ) {
					continue;
				}
				validatedInterfaces.put( declaringClass, clazz );
			}

			boolean tmp = validateMetaConstraint( validationContext, valueContext, valueContext.getCurrentBean(), metaConstraint );
			if ( shouldFailFast( validationContext ) ) {
				return false;
			}

			validationSuccessful = validationSuccessful && tmp;
		}
		return validationSuccessful;
	}

SimpleConstraintTree#validateConstraints,获取校验器。

	@Override
	protected void validateConstraints(ValidationContext<?> validationContext,
			ValueContext<?, ?> valueContext,
			Collection<ConstraintValidatorContextImpl> violatedConstraintValidatorContexts) {

		if ( LOG.isTraceEnabled() ) {
			LOG.tracef(
					"Validating value %s against constraint defined by %s.",
					valueContext.getCurrentValidatedValue(),
					descriptor
			);
		}

		// find the right constraint validator
		ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );

		// create a constraint validator context
		ConstraintValidatorContextImpl constraintValidatorContext = validationContext.createConstraintValidatorContextFor(
				descriptor, valueContext.getPropertyPath()
		);

		// validate
		if ( validateSingleConstraint( valueContext, constraintValidatorContext, validator ).isPresent() ) {
			violatedConstraintValidatorContexts.add( constraintValidatorContext );
		}
	}

ConstraintValidatorManagerImpl#getInitializedValidator,获取校验器

	public <A extends Annotation> ConstraintValidator<A, ?> getInitializedValidator(
			Type validatedValueType,
			ConstraintDescriptorImpl<A> descriptor,
			ConstraintValidatorFactory constraintValidatorFactory,
			HibernateConstraintValidatorInitializationContext initializationContext) {
		Contracts.assertNotNull( validatedValueType );
		Contracts.assertNotNull( descriptor );
		Contracts.assertNotNull( constraintValidatorFactory );
		Contracts.assertNotNull( initializationContext );

		CacheKey key = new CacheKey( descriptor.getAnnotationDescriptor(), validatedValueType, constraintValidatorFactory, initializationContext );

		@SuppressWarnings("unchecked")
		ConstraintValidator<A, ?> constraintValidator = (ConstraintValidator<A, ?>) constraintValidatorCache.get( key );

		if ( constraintValidator == null ) {
			constraintValidator = createAndInitializeValidator( validatedValueType, descriptor, constraintValidatorFactory, initializationContext );
			constraintValidator = cacheValidator( key, constraintValidator );
		}
		else {
			LOG.tracef( "Constraint validator %s found in cache.", constraintValidator );
		}

		return DUMMY_CONSTRAINT_VALIDATOR == constraintValidator ? null : constraintValidator;
	}

	protected <A extends Annotation> ConstraintValidator<A, ?> createAndInitializeValidator(
			Type validatedValueType,
			ConstraintDescriptorImpl<A> descriptor,
			ConstraintValidatorFactory constraintValidatorFactory,
			HibernateConstraintValidatorInitializationContext initializationContext) {

		ConstraintValidatorDescriptor<A> validatorDescriptor = findMatchingValidatorDescriptor( descriptor, validatedValueType );
		ConstraintValidator<A, ?> constraintValidator;

		if ( validatorDescriptor == null ) {
			return null;
		}

		constraintValidator = validatorDescriptor.newInstance( constraintValidatorFactory );
		initializeValidator( descriptor, constraintValidator, initializationContext );

		return constraintValidator;
	}

ConstraintTree#validateSingleConstraint,用校验器执行isValid方法

	protected final <V> Optional<ConstraintValidatorContextImpl> validateSingleConstraint(
			ValueContext<?, ?> valueContext,
			ConstraintValidatorContextImpl constraintValidatorContext,
			ConstraintValidator<A, V> validator) {
		boolean isValid;
		try {
			@SuppressWarnings("unchecked")
			V validatedValue = (V) valueContext.getCurrentValidatedValue();
			isValid = validator.isValid( validatedValue, constraintValidatorContext );
		}
		catch (RuntimeException e) {
			if ( e instanceof ConstraintDeclarationException ) {
				throw e;
			}
			throw LOG.getExceptionDuringIsValidCallException( e );
		}
		if ( !isValid ) {
			//We do not add these violations yet, since we don't know how they are
			//going to influence the final boolean evaluation
			return Optional.of( constraintValidatorContext );
		}
		return Optional.empty();
	}

非web接口校验

ValidationAutoConfiguration会注入MethodValidationPostProcessor

    @Bean
    @ConditionalOnMissingBean(
        search = SearchStrategy.CURRENT
    )
    public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator, ObjectProvider<MethodValidationExcludeFilter> excludeFilters) {
        FilteredMethodValidationPostProcessor processor = new FilteredMethodValidationPostProcessor(excludeFilters.orderedStream());
        boolean proxyTargetClass = (Boolean)environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
        processor.setProxyTargetClass(proxyTargetClass);
        processor.setValidator(validator);
        return processor;
    }

MethodValidationPostProcessor#afterPropertiesSet,创建切面和拦截器。

	private Class<? extends Annotation> validatedAnnotationType = Validated.class;

	@Override
	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());
	}

AbstractAdvisingBeanPostProcessor#postProcessAfterInitializationMethodValidationPostProcessor继承了AbstractAdvisingBeanPostProcessor,添加到advised 中,通过ProxyFactory 返回代理对象。

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		if (this.advisor == null || bean instanceof AopInfrastructureBean) {
			// Ignore AOP infrastructure such as scoped proxies.
			return bean;
		}

		if (bean instanceof Advised) {
			Advised advised = (Advised) bean;
			if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
				// Add our local Advisor to the existing proxy's Advisor chain...
				if (this.beforeExistingAdvisors) {
					advised.addAdvisor(0, this.advisor);
				}
				else {
					advised.addAdvisor(this.advisor);
				}
				return bean;
			}
		}

		if (isEligible(bean, beanName)) {
			ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
			if (!proxyFactory.isProxyTargetClass()) {
				evaluateProxyInterfaces(bean.getClass(), proxyFactory);
			}
			proxyFactory.addAdvisor(this.advisor);
			customizeProxyFactory(proxyFactory);

			// Use original ClassLoader if bean class not locally loaded in overriding class loader
			ClassLoader classLoader = getProxyClassLoader();
			if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {
				classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
			}
			return proxyFactory.getProxy(classLoader);
		}

		// No proxy needed.
		return bean;
	}

MethodValidationInterceptor#invoke,进行方法校验,校验不通过则报错。可以对参数和返回值进行校验。

	@Override
	@Nullable
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
		if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
			return invocation.proceed();
		}

		Class<?>[] groups = determineValidationGroups(invocation);

		// Standard Bean Validation 1.1 API
		ExecutableValidator execVal = this.validator.forExecutables();
		Method methodToValidate = invocation.getMethod();
		Set<ConstraintViolation<Object>> result;

		Object target = invocation.getThis();
		Assert.state(target != null, "Target must not be null");

		try {
			result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
		}
		catch (IllegalArgumentException ex) {
			// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
			// Let's try to find the bridged method on the implementation class...
			methodToValidate = BridgeMethodResolver.findBridgedMethod(
					ClassUtils.getMostSpecificMethod(invocation.getMethod(), target.getClass()));
			result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
		}
		if (!result.isEmpty()) {
			throw new ConstraintViolationException(result);
		}

		Object returnValue = invocation.proceed();

		result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups);
		if (!result.isEmpty()) {
			throw new ConstraintViolationException(result);
		}

		return returnValue;
	}

MethodValidationInterceptor#determineValidationGroups,获取方法或者类上的注解Validated

	protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
		Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
		if (validatedAnn == null) {
			Object target = invocation.getThis();
			Assert.state(target != null, "Target must not be null");
			validatedAnn = AnnotationUtils.findAnnotation(target.getClass(), Validated.class);
		}
		return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
	}
标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理 标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理 标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理 标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理 标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理

在这里插入图片描述

标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理
标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理
标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理
标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理
标题复制10行,并且每行大于10个字符【源码解析】SpringBoot接口参数校验原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值