欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

Spring controller校验入参的方法详解,

来源: javaer 分享于  点击 24165 次 点评:196

Spring controller校验入参的方法详解,


目录
  • 问题描述
  • 原因分析
    • 为什么@RequestBody中的@Valid生效了?
    • 为什么@RequestHeader中的@NotNull没有生效?
    • 怎么样才能生效?
    • @Validated生效原理
  • 总结

    问题描述

    项目中使用Springboot,在Controller中配置了@NotNull和@Valid,@Notnull不生效,@Valid生效,返回http status为400。

    @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解析的,下面重点看下这个。

    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);
    }
    
    protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
    	Annotation[] annotations = parameter.getParameterAnnotations();
    	Annotation[] var4 = annotations;
    	int var5 = annotations.length;
    
    	for(int var6 = 0; var6 < var5; ++var6) {
    		Annotation ann = var4[var6];
            // 重点,解析参数的注解
    		Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
    		if (validationHints != null) {
                // 执行校验
    			binder.validate(validationHints);
    			break;
    		}
    	}
    
    }
    

    可以看出,@Valid和@Validated注解都可以解析到:

    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依赖

    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    

    @Validated生效原理

    后处理器MethodValidationPostProcessor中给使用了@Validated注解的类创建了个切面。实际执行切面逻辑的是MethodValidationInterceptor

    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,

    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提供的了。

    private void validateMetaConstraints(BaseBeanValidationContext<?> validationContext, ValueContext<?, Object> valueContext, Object parent, Iterable<MetaConstraint<?>> 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校验入参的资料请关注3672js教程其它相关文章!

    您可能感兴趣的文章:
    • 关于SpringBoot中controller参数校验的使用
    • SpringBoot开发详解之Controller接收参数及参数校验
    • Springboot 使用 JSR 303 对 Controller 控制层校验及 Service 服务层 AOP 校验 使用消息资源文件对消息国际化
    • springboot controller参数注入方式
    相关栏目:

    用户点评