项目场景:
最近帮同事在看一个后端接口数据关于类型、长度、字符内容规范化的任务,想到了使用Valid注解的方式来完成,但是在实际使用的时候,发现前后端数据在不同场景下交互方式的差异,会导致Valid注解使用不生效。
问题描述
@RequestParam数据校验不生效:
public JsonResult createApplication( @NotBlank @Size(min = 1, max = 500) @RequestParam String filePath,
……
}
原因分析:
Spring在解析不同入参时,使用不同的解析器,有的解析器会触发Valid逻辑,有的不会。
@RequestParam等非body注解解析器不提供valid逻辑,详见org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
// Check for null value after conversion of incoming argument value
if (arg == null && namedValueInfo.defaultValue == null &&
namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
@RequestBody注解解析器会调用valid逻辑,详见org.springframework.web.method.annotation.ModelAttributeMethodProcessor#validateIfApplicable
protected void validateValueIfApplicable(WebDataBinder binder, MethodParameter parameter,
Class<?> targetType, String fieldName, @Nullable Object value) {
for (Annotation ann : parameter.getParameterAnnotations()) {
Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
if (validationHints != null) {
for (Validator validator : binder.getValidators()) {
if (validator instanceof SmartValidator) {
try {
((SmartValidator) validator).validateValue(targetType, fieldName, value,
binder.getBindingResult(), validationHints);
}
catch (IllegalArgumentException ex) {
// No corresponding field on the target class...
}
}
}
break;
}
}
}
解决方案:
Spring解析参数的逻辑没办法调整,只能在方法调用时进行更改。
如果要在方法层面使用valid注解,就需要添加一个bean:org.springframework.validation.beanvalidation.MethodValidationPostProcessor
@Configuration
@EnableAutoConfiguration
public class ConfigurationFactory {
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor()
{
return new MethodValidationPostProcessor();
}
}
然后再添加一个全局异常捕获就好了。
@Slf4j
@RestControllerAdvice
public class GlobalException {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({HandlerMethodValidationException.class})
public Result handlerMethodValidationException(HandlerMethodValidationException e) {
// 判断异常中是否有错误信息,如果存在就使用异常中的消息,否则使用默认消息
if (!StringUtils.isEmpty(e.getMessage())) {
return ResultUtil.failureMsg(e.getMessage());
}
return ResultUtil.failure(ResultEnum.SERVICE_FAILURE);
}
}