Spring controller校验入参的方法详解

 更新时间:2024年06月07日 08:27:02   作者:有信仰  
项目中使用Springboot,在Controller中配置了@NotNull和@Valid,@Notnull不生效,@Valid生效,返回http status为400,本文给大家介绍了Spring controller校验入参的方法,需要的朋友可以参考下

问题描述

项目中使用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校验入参的资料请关注脚本之家其它相关文章!

相关文章

  • springboot引用kettle实现对接oracle数据的示例代码

    springboot引用kettle实现对接oracle数据的示例代码

    这篇文章主要介绍了springboot引用kettle实现对接oracle数据,其实kettle集成到springboot里面没有多少代码,这个功能最主要的还是ktr文件的编写,只要ktr编写好了,放到指定文件夹下,写个定时任务就完事了,需要的朋友可以参考下
    2022-12-12
  • MyBatis常用标签大全

    MyBatis常用标签大全

    这篇文章主要介绍了MyBatis常用标签大全的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-12-12
  • spring项目对某条单据进行加锁处理的方法

    spring项目对某条单据进行加锁处理的方法

    这篇文章主要给大家介绍了关于spring项目对某条单据进行加锁处理的相关资料,用于对工单单据进行加锁和解锁处理,以防止多用户同时编辑同一单据,前端传递参数包括单据ID、类型、锁超时时间等,后端通过Redis实现锁机制,需要的朋友可以参考下
    2024-11-11
  • Java开发基础日期类代码详解

    Java开发基础日期类代码详解

    这篇文章主要介绍了Java开发基础日期类的相关内容,代码通过日期工具类获取指定月份的星期与日期对应关系,以及获取指定月份的所有日期与星期集合等,具有一定参考价值,需要的朋友可以了解下。
    2017-10-10
  • Java中的==使用方法详解

    Java中的==使用方法详解

    这篇文章主要给大家介绍了关于Java中的==使用方法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-09-09
  • 如何解决java.util.zip.ZipFile解压后被java占用问题

    如何解决java.util.zip.ZipFile解压后被java占用问题

    这篇文章主要介绍了如何解决java.util.zip.ZipFile解压后被java占用问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • 并发编程之Java内存模型

    并发编程之Java内存模型

    这篇文章主要介绍了Java并发编程之内存模型,Java内存模型中的顺序一致性,主要介绍重排序与顺序一致性内存模型,下面文章将围绕Java内存模型展开内容,需要的小伙伴可以参考一下
    2021-11-11
  • 详解RestTemplate的三种使用方式

    详解RestTemplate的三种使用方式

    这篇文章主要介绍了详解RestTemplate的三种使用方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-10-10
  • 解决SpringBoot中的Scheduled单线程执行问题

    解决SpringBoot中的Scheduled单线程执行问题

    在一次SpringBoot中使用Scheduled定时任务时,发现某一个任务出现执行占用大量资源,会导致其他任务也执行失败,这篇文章主要介绍了SpringBoot中的Scheduled单线程执行问题及解决方法,需要的朋友可以参考下
    2022-06-06
  • IDEA与模拟器安装调试失败的处理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES

    IDEA与模拟器安装调试失败的处理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES

    这篇文章主要介绍了IDEA与模拟器安装调试失败的处理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09

最新评论