问题描述
项目中使用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校验入参的资料请关注代码网其它相关文章!
发表评论