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ù)解析,解析相關的類:

為什么@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,里面并沒有調用validate相關的代碼。
怎么樣才能生效?
在類上加@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;
}
}
}
總結
controller中requestBody中直接可以用@Valid或@Validated校驗,如果想校驗方法中單個參數(shù),需要在方法或類上加@Validated,這樣會開啟方法校驗的切面,切面中會拿到方法簽名中每個字段的注解然后進行校驗。
以上就是Spring controller校驗入?yún)⒌姆椒ㄔ斀獾脑敿殐热?,更多關于Spring controller校驗入?yún)⒌馁Y料請關注腳本之家其它相關文章!
相關文章
-
Java安全框架——Shiro的使用詳解(附springboot整合Shiro的demo)
這篇文章主要介紹了Java安全框架——Shiro的使用詳解,幫助大家更好的理解和學習使用Shiro,感興趣的朋友可以了解下 2021-04-04
-
基于java ssm springboot+mybatis酒莊內部管理系統(tǒng)設計和實現(xiàn)
這篇文章主要為大家詳細介紹了java ssm springboot+mybatis實現(xiàn)酒店管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下 2021-08-08
-
Java 中 Function 與 apply 的實際應用場景分析(含
本文介紹了如何使用Java 8的Function函數(shù)式接口簡化重復邏輯,包括判空、格式化、字段提取等常見場景,通過實際案例,展示了如何將多個字段的處理邏輯集中到一個函數(shù)中,從而提高代碼的可讀性和可維護性,感興趣的朋友跟隨小編一起看看吧 2025-12-12
-
Java之SpringCloud Eurka注冊錯誤解決方案
這篇文章主要介紹了Java之SpringCloud Eurka注冊錯誤解決方案,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下 2021-07-07
-
Java利用Socket實現(xiàn)網(wǎng)絡通信功能
在早期的網(wǎng)絡編程中,Socket是很常見的實現(xiàn)技術之一,比如早期的聊天室,就是基于這種技術進行實現(xiàn)的,另外現(xiàn)在有些消息推送,也可以基于Socket實現(xiàn),本文小編給大家介紹了Java利用Socket實現(xiàn)網(wǎng)絡通信功能的示例,需要的朋友可以參考下 2023-11-11
最新評論
問題描述
項目中使用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ù)解析,解析相關的類:

為什么@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,里面并沒有調用validate相關的代碼。
怎么樣才能生效?
在類上加@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;
}
}
}
總結
controller中requestBody中直接可以用@Valid或@Validated校驗,如果想校驗方法中單個參數(shù),需要在方法或類上加@Validated,這樣會開啟方法校驗的切面,切面中會拿到方法簽名中每個字段的注解然后進行校驗。
以上就是Spring controller校驗入?yún)⒌姆椒ㄔ斀獾脑敿殐热?,更多關于Spring controller校驗入?yún)⒌馁Y料請關注腳本之家其它相關文章!
相關文章
Java安全框架——Shiro的使用詳解(附springboot整合Shiro的demo)
這篇文章主要介紹了Java安全框架——Shiro的使用詳解,幫助大家更好的理解和學習使用Shiro,感興趣的朋友可以了解下2021-04-04
基于java ssm springboot+mybatis酒莊內部管理系統(tǒng)設計和實現(xiàn)
這篇文章主要為大家詳細介紹了java ssm springboot+mybatis實現(xiàn)酒店管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-08-08
Java 中 Function 與 apply 的實際應用場景分析(含
本文介紹了如何使用Java 8的Function函數(shù)式接口簡化重復邏輯,包括判空、格式化、字段提取等常見場景,通過實際案例,展示了如何將多個字段的處理邏輯集中到一個函數(shù)中,從而提高代碼的可讀性和可維護性,感興趣的朋友跟隨小編一起看看吧2025-12-12
Java之SpringCloud Eurka注冊錯誤解決方案
這篇文章主要介紹了Java之SpringCloud Eurka注冊錯誤解決方案,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-07-07
Java利用Socket實現(xiàn)網(wǎng)絡通信功能
在早期的網(wǎng)絡編程中,Socket是很常見的實現(xiàn)技術之一,比如早期的聊天室,就是基于這種技術進行實現(xiàn)的,另外現(xiàn)在有些消息推送,也可以基于Socket實現(xiàn),本文小編給大家介紹了Java利用Socket實現(xiàn)網(wǎng)絡通信功能的示例,需要的朋友可以參考下2023-11-11

