????????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---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;
}
}
}
|