@Valid
和@Validated
结论写在前面
Spring中使用
- 参考自:
MethodValidationPostProcessor
@Validated
需要标记在类上才会生成代理对象,参考
MethodValidationInterceptor.determineValidationGroups
,可以在方法上加@Validated实现分组
SprinvMVC中
- 参考自:
RequestResponseBodyMethodProcessor
@Valid
不支持分组校验
@Valid
注解内部是空的,没有任何属性
@Validated
支持分组,但是无法在嵌套中分组
两个注解在代码中唯一的不同就是
determineValidationHints
方法,该方法返回的校验的分组标识类,@Validated及扩展注解支持分组但是Spring仅仅是封装了
org.hibernate.validator.internal.engine.ValidatorImpl
,校验的执行逻辑也是该类负责执行; 对于嵌套的校验,@Validated
属于外来注解, 因此嵌套内只识别@Valid
注解@Validated不支持嵌套分组
源码跟踪<Version:SpringBoot spring-boot.version,Spring: 5.3.9
>
两个注解类代码
//类, 方法, 参数
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
Class<?>[] value() default {};
}
//方法, 字段, 构造器, 参数, 泛型
@Target({ METHOD, FIELD, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface Valid {
}
LocalValidatorFactoryBean
的来源
PrimaryDefaultValidatorPostProcessor
class PrimaryDefaultValidatorPostProcessor implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
//validator在springBoot中默认的bean名称
private static final String VALIDATOR_BEAN_NAME = "defaultValidator";
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinition definition = getAutoConfiguredValidator(registry);
//如果容器中没有其他Validator,这个默认的就是Primary的Bean
if (definition != null) {
definition.setPrimary(!hasPrimarySpringValidator());
}
}
private BeanDefinition getAutoConfiguredValidator(BeanDefinitionRegistry registry) {
if (registry.containsBeanDefinition(VALIDATOR_BEAN_NAME)) {
BeanDefinition definition = registry.getBeanDefinition(VALIDATOR_BEAN_NAME);
//如果没有指定,那么默认情况下Validator的工厂bean为:`LocalValidatorFactoryBean`
if (definition.getRole() == BeanDefinition.ROLE_INFRASTRUCTURE
&& isTypeMatch(VALIDATOR_BEAN_NAME, LocalValidatorFactoryBean.class)) {
return definition;
}
}
return null;
}
}
Spring
测试代码
@Service
@Validated
public class ValidateService {
Random random = new Random();
public @NotBlank String getMessage(@NotBlank String message) {
if (random.nextBoolean()) {
return message;
}
return null;
}
}
实例化入口
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ExecutableValidator.class)
//资源对应在`hibernate-validator`包中
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
//创建Validator的入口
@Import(PrimaryDefaultValidatorPostProcessor.class)
public class ValidationAutoConfiguration {
}
执行处理器
MethodValidationPostProcessor
/**
* 简单的翻译:
* 一个BeanPostprocessor的实现类
* 适用于入参和返回值
* 目标类需要标记Spring的Validated注解在他的类级别上以便于他们方法建立约束
*
* A convenient {@link BeanPostProcessor} implementation that delegates to a
* JSR-303 provider for performing method-level validation on annotated methods.
* <p>Applicable methods have JSR-303 constraint annotations on their parameters
* and/or on their return value (in the latter case specified at the method level,
* typically as inline annotation), e.g.:
*
* <pre class="code">
* public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)
* </pre>
*
* <p>Target classes with such annotated methods need to be annotated with Spring's
* {@link Validated} annotation at the type level, for their methods to be searched for
* inline constraint annotations. Validation groups can be specified through {@code @Validated}
* as well. By default, JSR-303 will validate against its default group only.
*
* <p>As of Spring 5.0, this functionality requires a Bean Validation 1.1+ provider.
* @see javax.validation.executable.ExecutableValidator
*/
@SuppressWarnings("serial")
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
implements InitializingBean {
@Override
public void afterPropertiesSet() {
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}
}
拦截器处理类
MethodValidationInterceptor
public class MethodValidationInterceptor implements MethodInterceptor {
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
//根据group进行分组 , 进支持Validated注解; 不支持扩展注解了
Class<?>[] groups = determineValidationGroups(invocation);
//...
//proceed执行前对入参校验
result = execVal.validateParameters(target, methodToValidate, invocation.getArguments(), groups);
//方法执行
Object returnValue = invocation.proceed();
//proceed执行后对结果校验
result = execVal.validateReturnValue(target, methodToValidate, returnValue, groups);
//...
}
}
SpringMVC
入口
测试代码
@PostMapping("/valid")
public ResponseEntity valid(@Valid @RequestBody User user) {
System.out.println("user = " + user);
return ResponseEntity.ok().build();
}
实例化入口
springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
...
//默认取得就是Spring中实例化的对象
@Bean
public Validator mvcValidator() {
return !ClassUtils.isPresent("javax.validation.Validator", this.getClass().getClassLoader()) ? super.mvcValidator() : ValidatorAdapter.get(this.getApplicationContext(), this.getValidator());
}
...
}
执行处理器:
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
/**
* Throws MethodArgumentNotValidException if validation fails.
* @throws HttpMessageNotReadableException if {@link RequestBody#required()}
* is {@code true} and there is no body content or if there is no suitable
* converter to read the content with.
*/
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
/**
* Validate the binding target if applicable.
* <p>The default implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param binder the DataBinder to be used
* @param parameter the method parameter descriptor
*/
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
if (validationHints != null) {
binder.validate(validationHints);
break;
}
}
}
@Valid
处理不了分组的原因代码
determineValidationHints(ann)
/**
* Valid返回空;
* Validated和Valid前缀的; 返回value对应的类<分组标志>
* Determine any validation hints by the given annotation.
* <p>This implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param ann the annotation (potentially a validation annotation)
* @return the validation hints to apply (possibly an empty array),
* or {@code null} if this annotation does not trigger any validation
*/
@Nullable
public static Object[] determineValidationHints(Annotation ann) {
Class<? extends Annotation> annotationType = ann.annotationType();
String annotationName = annotationType.getName();
if ("javax.validation.Valid".equals(annotationName)) {
return EMPTY_OBJECT_ARRAY;
}
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null) {
Object hints = validatedAnn.value();
return convertValidationHints(hints);
}
if (annotationType.getSimpleName().startsWith("Valid")) {
Object hints = AnnotationUtils.getValue(ann);
return convertValidationHints(hints);
}
return null;
}