Spring?controller校驗入?yún)⒌姆椒ㄔ斀?/h1>
更新時間:2024年06月07日 08:27:02 作者:有信仰
項目中使用Springboot,在Controller中配置了@NotNull和@Valid,@Notnull不生效,@Valid生效,返回http?status為400,本文給大家介紹了Spring?controller校驗入?yún)⒌姆椒?需要的朋友可以參考下
問題描述
項目中使用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) {
// 業(yè)務邏輯
}
}
原因分析
controller接收到請求,首先會進行參數(shù)解析,解析相關(guān)的類:

為什么@RequestBody中的@Valid生效了?
參數(shù)中@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];
// 重點,解析參數(shù)的注解
Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
if (validationHints != null) {
// 執(zhí)行校驗
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,里面并沒有調(diào)用validate相關(guān)的代碼。
怎么樣才能生效?
在類上加@Validated。并且加maven依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@Validated生效原理
后處理器MethodValidationPostProcessor中給使用了@Validated注解的類創(chuàng)建了個切面。實際執(zhí)行切面邏輯的是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();
}
}
請求執(zhí)行時,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。代碼一直跟下去,能找到最終執(zhí)行校驗的地方。---注意,ValidatorImpl已經(jīng)是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;
}
}
}
總結(jié)
controller中requestBody中直接可以用@Valid或@Validated校驗,如果想校驗方法中單個參數(shù),需要在方法或類上加@Validated,這樣會開啟方法校驗的切面,切面中會拿到方法簽名中每個字段的注解然后進行校驗。
以上就是Spring controller校驗入?yún)⒌姆椒ㄔ斀獾脑敿殐?nèi)容,更多關(guān)于Spring controller校驗入?yún)⒌馁Y料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
-
springboot引用kettle實現(xiàn)對接oracle數(shù)據(jù)的示例代碼
這篇文章主要介紹了springboot引用kettle實現(xiàn)對接oracle數(shù)據(jù),其實kettle集成到springboot里面沒有多少代碼,這個功能最主要的還是ktr文件的編寫,只要ktr編寫好了,放到指定文件夾下,寫個定時任務就完事了,需要的朋友可以參考下 2022-12-12
-
如何解決java.util.zip.ZipFile解壓后被java占用問題
這篇文章主要介紹了如何解決java.util.zip.ZipFile解壓后被java占用問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教 2024-06-06
-
解決SpringBoot中的Scheduled單線程執(zhí)行問題
在一次SpringBoot中使用Scheduled定時任務時,發(fā)現(xiàn)某一個任務出現(xiàn)執(zhí)行占用大量資源,會導致其他任務也執(zhí)行失敗,這篇文章主要介紹了SpringBoot中的Scheduled單線程執(zhí)行問題及解決方法,需要的朋友可以參考下 2022-06-06
-
IDEA與模擬器安裝調(diào)試失敗的處理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES
這篇文章主要介紹了IDEA與模擬器安裝調(diào)試失敗的處理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下 2020-09-09
最新評論
問題描述
項目中使用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) { // 業(yè)務邏輯 } }
原因分析
controller接收到請求,首先會進行參數(shù)解析,解析相關(guān)的類:
為什么@RequestBody中的@Valid生效了?
參數(shù)中@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]; // 重點,解析參數(shù)的注解 Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann); if (validationHints != null) { // 執(zhí)行校驗 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,里面并沒有調(diào)用validate相關(guān)的代碼。
怎么樣才能生效?
在類上加@Validated。并且加maven依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
@Validated生效原理
后處理器MethodValidationPostProcessor中給使用了@Validated注解的類創(chuàng)建了個切面。實際執(zhí)行切面邏輯的是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(); } }
請求執(zhí)行時,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。代碼一直跟下去,能找到最終執(zhí)行校驗的地方。---注意,ValidatorImpl已經(jīng)是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; } } }
總結(jié)
controller中requestBody中直接可以用@Valid或@Validated校驗,如果想校驗方法中單個參數(shù),需要在方法或類上加@Validated,這樣會開啟方法校驗的切面,切面中會拿到方法簽名中每個字段的注解然后進行校驗。
以上就是Spring controller校驗入?yún)⒌姆椒ㄔ斀獾脑敿殐?nèi)容,更多關(guān)于Spring controller校驗入?yún)⒌馁Y料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot引用kettle實現(xiàn)對接oracle數(shù)據(jù)的示例代碼
這篇文章主要介紹了springboot引用kettle實現(xiàn)對接oracle數(shù)據(jù),其實kettle集成到springboot里面沒有多少代碼,這個功能最主要的還是ktr文件的編寫,只要ktr編寫好了,放到指定文件夾下,寫個定時任務就完事了,需要的朋友可以參考下2022-12-12如何解決java.util.zip.ZipFile解壓后被java占用問題
這篇文章主要介紹了如何解決java.util.zip.ZipFile解壓后被java占用問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06解決SpringBoot中的Scheduled單線程執(zhí)行問題
在一次SpringBoot中使用Scheduled定時任務時,發(fā)現(xiàn)某一個任務出現(xiàn)執(zhí)行占用大量資源,會導致其他任務也執(zhí)行失敗,這篇文章主要介紹了SpringBoot中的Scheduled單線程執(zhí)行問題及解決方法,需要的朋友可以參考下2022-06-06IDEA與模擬器安裝調(diào)試失敗的處理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES
這篇文章主要介紹了IDEA與模擬器安裝調(diào)試失敗的處理方法:INSTALL_PARSE_FAILED_NO_CERTIFICATES,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09