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