问题描述
项目中使用Springboot,在Controller中配置了@NotNull和@Valid,@Notnull不生效,@Valid生效,返回http status为400。
1 2 3 4 5 6 7 8 9 10 11 | @RestController @RequestMapping ( "/demo" ) public class DemoController { @Override @PostMapping ( "/user" ) public CreateUserRsp createUser( @NotNull @Size (min = 1 , max = 64 ) @RequestHeader (value = "token" ) String token, @NotNull @Valid @RequestBody CreateUserReq createUserReq) { // 业务逻辑 } } |
原因分析
controller接收到请求,首先会进行参数解析,解析相关的类:
为什么@RequestBody中的@Valid生效了?
参数中@RequestBody注解是使用RequestResponseBodyMethodProcessor解析的,下面重点看下这个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional(); Object arg = this .readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); String name = Conventions.getVariableNameForParameter(parameter); if (binderFactory != null ) { WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null ) { // 重点 this .validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && this .isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } if (mavContainer != null ) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); } } return this .adaptArgumentIfNecessary(arg, parameter); } |
1 2 3 4 5 6 | protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) { Annotation[] annotations = parameter.getParameterAnnotations(); Annotation[] var4 = annotations; int var5 = annotations.length; for ( int var6 = 0 ; var6 |
可以看出,@Valid和@Validated注解都可以解析到:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public static Object[] determineValidationHints(Annotation ann) { if (ann instanceof Validated) { return ((Validated)ann).value(); } else { Class extends Annotation> annotationType = ann.annotationType(); if ( "javax.validation.Valid" .equals(annotationType.getName())) { return EMPTY_OBJECT_ARRAY; } else { Validated validatedAnn = (Validated)AnnotationUtils.getAnnotation(ann, Validated. class ); if (validatedAnn != null ) { return validatedAnn.value(); } else { return annotationType.getSimpleName().startsWith( "Valid" ) ? convertValidationHints(AnnotationUtils.getValue(ann)) : null ; } } } } |
为什么@RequestHeader中的@NotNull没有生效?
按照上面的思路,我们看下RequestHeaderMapMethodArgumentResolver,里面并没有调用validate相关的代码。
怎么样才能生效?
在类上加@Validated。并且加maven依赖
1 | org.springframework.bootspring-boot-starter-validation |
@Validated生效原理
后处理器MethodValidationPostProcessor中给使用了@Validated注解的类创建了个切面。实际执行切面逻辑的是MethodValidationInterceptor
1 2 3 4 5 6 7 8 9 10 11 | public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor implements InitializingBean { private Class extends Annotation> validatedAnnotationType = Validated. class ; public void afterPropertiesSet() { Pointcut pointcut = new AnnotationMatchingPointcut( this .validatedAnnotationType, true ); this .advisor = new DefaultPointcutAdvisor(pointcut, this .createMethodValidationAdvice( this .validator)); } protected Advice createMethodValidationAdvice( @Nullable Validator validator) { return validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor(); } } |
请求执行时,MethodValidationInterceptor中先判断方法和类上有没有@Validated,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public Object invoke(MethodInvocation invocation) throws Throwable { if ( this .isFactoryBeanMetadataMethod(invocation.getMethod())) { return invocation.proceed(); } else { // 方法和类上有没有@Validated Class>[] groups = this .determineValidationGroups(invocation); ExecutableValidator execVal = this .validator.forExecutables(); Method methodToValidate = invocation.getMethod(); Object target = invocation.getThis(); Assert.state(target != null , "Target must not be null" ); Set result; try { // 校验 result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups); } catch (IllegalArgumentException var8) { methodToValidate = BridgeMethodResolver.findBridgedMethod(ClassUtils.getMostSpecificMethod(invocation.getMethod(), target.getClass())); result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups); } if (!result.isEmpty()) { // 校验失败的异常 throw new ConstraintViolationException(result); } else { Object returnValue = invocation.proceed(); result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups); if (!result.isEmpty()) { throw new ConstraintViolationException(result); } else { return returnValue; } } } } |
实际校验的类是ValidatorImpl。代码一直跟下去,能找到最终执行校验的地方。—注意,ValidatorImpl已经是hibernate-validator提供的了。
1 2 3 4 5 6 7 8 9 10 11 12 | private void validateMetaConstraints(BaseBeanValidationContext> validationContext, ValueContext, Object> valueContext, Object parent, Iterable> constraints) { Iterator var5 = constraints.iterator(); while (var5.hasNext()) { MetaConstraint> metaConstraint = (MetaConstraint)var5.next(); this .validateMetaConstraint(validationContext, valueContext, parent, metaConstraint); if ( this .shouldFailFast(validationContext)) { break ; } } } |
总结
controller中requestBody中直接可以用@Valid或@Validated校验,如果想校验方法中单个参数,需要在方法或类上加@Validated,这样会开启方法校验的切面,切面中会拿到方法签名中每个字段的注解然后进行校验。
以上就是Spring controller校验入参的方法详解的详细内容,更多关于Spring controller校验入参的资料请关注IT俱乐部其它相关文章!