一般情况下我们会发现对GET请求的入参做参数校验时抛出ConstraintViolationException异常,而对POST请求的RequestBody做参数校验时会抛出MethodArgumentNotValidException异常。这与Spring中绑定GET参数和POST参数的方式不同有关。
Post绑定参数依靠WebDataBinder来实现将参数绑定到JavaBean对象上。
RequestResponseBodyMethodProcessor中的resolveArgument会将MethodParameter转换为controller中定义的JavaBean。
@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);
}
其中validateIfApplicable在对parameter进行校验,若binder绑定的结果中有错误,则会抛出MethodArgumentNotValidException异常。
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
binder.validate(validationHints);
break;
}
}
}
从validateIfApplicable中可以发现参数校验生效的条件是能找到@Validated注解或者其他以Valid开头的注解。这与Springboot2.x中的hibernate-validator(1)GET请求中以@Validated注解为切点的方式不一样。
binder.validate
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之后就与Springboot2.x中的hibernate-validator(1)GET请求类似了。
剩下需要探讨的问题就是这两个所使用的validator是同一个吗?