IT俱乐部 Java Spring controller校验入参的方法详解

Spring controller校验入参的方法详解

问题描述

项目中使用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俱乐部其它相关文章!

本文收集自网络,不代表IT俱乐部立场,转载请注明出处。https://www.2it.club/code/java/11840.html
上一篇
下一篇
联系我们

联系我们

在线咨询: QQ交谈

邮箱: 1120393934@qq.com

工作时间:周一至周五,9:00-17:30,节假日休息

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部