springboot中@Vlidated注解校验源码

        springboot中支持JSR-303校验规范,该规范的默认实现为hibernate,关于常用校验注解的使用参考:springboot中注解校验@Valid@Validated(亲测有效)_卖柴火的小伙子的博客-CSDN博客

本文重在原理,下面从常用的两种注解校验方式看一下源码原理.

1.请求单个参数注解

        此种实现方式是利用cglib动态代理动态获取方法拦截切面,主要逻辑在MethodValidationInterceptor.java


	public Object invoke(MethodInvocation invocation) throws Throwable {
		// Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
		if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
			return invocation.proceed();
		}
        
        // 获取标注在方法或是类上的@Vlidated注解中的value属性值.@Validated中value值用于分组校验的标识.
		Class<?>[] groups = determineValidationGroups(invocation);

		// ExecutableValidator是hibernate校验的核心处理类
		ExecutableValidator execVal = this.validator.forExecutables();
        // 获取拦截的方法class对象
		Method methodToValidate = invocation.getMethod();
		Set<ConstraintViolation<Object>> result;

        // 获取方法拦截的所在的class类对象
		Object target = invocation.getThis();
		Assert.state(target != null, "Target must not be null");

		try {
            // 调用hibernate校验的核心处理类进行请求参数校验
			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();
        // 调用hibernate校验的核心处理类进行响应参数校验,并返回响应参数
		result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups);
		if (!result.isEmpty()) {
			throw new ConstraintViolationException(result);
		}

		return returnValue;
	}

// 获取所在的类或是方法上的@Vlidated注解的value属性值.@Validated中value值用于分组校验的标识.
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
    // 首先获取方法上是否有@Valited注解,如果没有则看类上是否有此注解.		
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);
		}
        // 此处获取的是@Validated注解中标注的value属性值.
		return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
	}

ValidatorImpl.java----注解校验的实现类

private <T> Set<ConstraintViolation<T>> validateParameters(T object, ExecutableElement executable, Object[] parameterValues, Class<?>... groups) {
		//this might be the case for parameterless methods
		if ( parameterValues == null ) {
			return Collections.emptySet();
		}
        
        // 根据@Validated注解的属性构建校验顺序处理器
		ValidationOrder validationOrder = determineGroupValidationOrder( groups );

        // 根据配置构建注解校验上下文
		ValidationContext<T> context = getValidationContext().forValidateParameters(
				parameterNameProvider,
				object,
				executable,
				parameterValues
		);

		if ( !beanMetaDataManager.isConstrained( context.getRootBeanClass() ) ) {
			return Collections.emptySet();
		}

        // 参数校验的核心逻辑:从注解校验上下文中进行参数校验
		validateParametersInContext( context, parameterValues, validationOrder );
         
        // 校验结束后从注解校验上下文中获取失败约束校验信息
		return context.getFailingConstraints();
	}

validateParametersInContext中方法比较复杂,大概执行的逻辑是按照分组、参数、是否级联操作、是否遇到错误校验就直接结束(FailFast)等,此处只讲核心处理逻辑:

ConstraintTree.java--validateConstraints(校验器初始化与校验)

private <T, V> void validateConstraints(ValidationContext<T> validationContext,
			ValueContext<?, V> valueContext,
			Set<ConstraintViolation<T>> constraintViolations) {
		CompositionResult compositionResult = validateComposingConstraints(
				validationContext, valueContext, constraintViolations
		);

		Set<ConstraintViolation<T>> localViolations;

		    // 省略部分代码......

			// 获取注解对应的校验器(XXXValidator),并执行校验器中的初始化方法(initialize)
			ConstraintValidator<A, V> validator = getInitializedConstraintValidator( validationContext, valueContext );

			// 创建约束校验上下文(数据组装)
			ConstraintValidatorContextImpl constraintValidatorContext = new ConstraintValidatorContextImpl(
					validationContext.getParameterNames(),
					validationContext.getTimeProvider(),
					valueContext.getPropertyPath(),
					descriptor
			);

			// 校验器(XXXValidator)执行校验(isValid)逻辑,校验组装参数封装在localViolations 
			localViolations = validateSingleConstraint(
					validationContext,
					valueContext,
					constraintValidatorContext,
					validator
			);

		    // 省略部分逻辑
	}

ConstraintTree.java---validateSingleConstraint(校验器校验处理)

private <T, V> Set<ConstraintViolation<T>> validateSingleConstraint(ValidationContext<T> executionContext,
			ValueContext<?, ?> valueContext,
			ConstraintValidatorContextImpl constraintValidatorContext,
			ConstraintValidator<A, V> validator) {
		boolean isValid;
		try {
			@SuppressWarnings("unchecked")    
            // 获取方法上的参数的具体值
			V validatedValue = (V) valueContext.getCurrentValidatedValue();
            // 具体校验逻辑:示例中使用的是@Min,此处的validator实现类是MinValidatorForNumber
			isValid = validator.isValid( validatedValue, constraintValidatorContext );
		}
		// 省略部分代码...........
		return Collections.emptySet();
	}

MinValidatorForNumber .java实现参数值大小校验的逻辑:

public class MinValidatorForNumber implements ConstraintValidator<Min, Number> {

	private long minValue;

	public void initialize(Min minValue) {
		this.minValue = minValue.value();
	}
        
    // @Min参数校验的具体逻辑,先判断数据类型然后比较大小.
	public boolean isValid(Number value, ConstraintValidatorContext constraintValidatorContext) {
		// null values are valid
		if ( value == null ) {
			return true;
		}

		//handling of NaN, positive infinity and negative infinity
		else if ( value instanceof Double ) {
			if ( (Double) value == Double.POSITIVE_INFINITY ) {
				return true;
			}
			else if ( Double.isNaN( (Double) value ) || (Double) value == Double.NEGATIVE_INFINITY ) {
				return false;
			}
		}
		else if ( value instanceof Float ) {
			if ( (Float) value == Float.POSITIVE_INFINITY ) {
				return true;
			}
			else if ( Float.isNaN( (Float) value ) || (Float) value == Float.NEGATIVE_INFINITY ) {
				return false;
			}
		}

		if ( value instanceof BigDecimal ) {
			return ( (BigDecimal) value ).compareTo( BigDecimal.valueOf( minValue ) ) != -1;
		}
		else if ( value instanceof BigInteger ) {
			return ( (BigInteger) value ).compareTo( BigInteger.valueOf( minValue ) ) != -1;
		}
		else {
			long longValue = value.longValue();
			return longValue >= minValue;
		}
	}
}

至此,参数校验逻辑结束,校验错误信息在此不做说明.

2.请求对象参数注解

springMVC中RequestResponseBodyMethodProcessor.java专门对请求响应体做参数处理.解析参数,具体单个参数校验逻辑同上.

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
	// 获取参数上的注解数组	
    Annotation[] annotations = parameter.getParameterAnnotations();
		for (Annotation ann : annotations) {
            // 获取校验注解数组(校验注解以valid开头或直接是@Validated注解)
			Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
			if (validationHints != null) {
                // 绑定数据校验的具体逻辑,校验的具体实现:ConstraintTree.java中validateSingleConstraint
				binder.validate(validationHints);
				break;
			}
		}
	}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卖柴火的小伙子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值