@Valid和@Validated

@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.

 * 

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.: * *

 * public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)
 * 
* *

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. * *

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.
* 

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.
     * 

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 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; }

你可能感兴趣的:(@Valid和@Validated)