@Validated和@Valid的区别、用法

72 阅读6分钟

参考文章

一、区别

先总结一下它们的区别:

  1. 来源
    • @org.springframework.validation.annotation.Validated :是Spring框架特有的注解,属于Spring的一部分,也是JSR 303的一个变种。它提供了一些 @Valid 所没有的额外功能,比如分组验证。
    • @jakarta.validation.Valid:Java EE提供的标准注解,它是JSR 303规范的一部分,主要用于Hibernate Validation等场景。 说明:jakarta.* 包名的旧名称是 javax.*
  2. 注解位置
    • @Validated : 用在类、方法、方法参数上。但不能用于成员属性。
    • @Valid:可以用在方法、构造函数、方法参数、成员属性上。
  3. 分组
    • @Validated :支持分组验证,可以更细致地控制验证过程。此外,由于它是Spring专有的,因此可以更好地与Spring的其他功能(如Spring的依赖注入)集成。
    • @Valid:主要支持标准的Bean验证功能,不支持分组验证。
  4. 嵌套验证
    • @Validated :不支持嵌套验证。
    • @Valid:支持嵌套验证,可以嵌套验证对象内部的属性。
  5. @Validated@Valid 是验证开关注解,需要配合 jakarta.validation.constraints 包下参数约束注解使用

所有参数注解含义

二、@Valid 用法(导错包不生效)

当 Spring Boot 的版本小于2.3.x,spring-boot-starter-web会自动引入hibernate-validator依赖。

当 Spring Boot 的版本大于2.3.x时,则需要手动引入hibernate-validator依赖。

必须引入 spring-boot-starter-validation依赖

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

2.1、实体属性添加

1.单个实体

public class User {
    @NotBlank(message = "用户名不为空")
    private String username;
    @NotBlank(message = "密码不为空")
    private String password;
}

2.嵌套实体

public class User {
    @NotBlank(message = "用户名不为空")
    private String username;
    @NotBlank(message = "密码不为空")
    private String password;
    
    @Valid //不加此注解,则不会校验
    @NotNull(message = "用户住址不能为空")
    private Address address;
}
public class Address {
    @NotBlank(message = "地址详情不能为空")
    private String info;
}

2.2、接口添加

    @PostMapping("/user")
    public void addUserInfo(@RequestBody @Valid User user) {
       
    }

2.3、全局异常捕获

@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)//最高优先级,防止有其他的全局异常捕获导致这个无法捕获
public class ExceptionAdvice {
    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    public R handleValidException(MethodArgumentNotValidException m){
            Map<String,String> errorMsg = m.getBindingResult.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField,FieldError::getDefaultMessage,(k1,k2) -> k1));
            return R.FAILED("数据异常").setData(errorMsg);
    }
}

三、@Validated 分组用法

分组是在实体类验证中常用的一种技术,它允许你根据不同的场景对验证规则进行分组,从而在不同的情况下应用不同的验证规则。

@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
public class UserCopyVo {
    @NotEmpty(groups = update.class, message = 'id不能为空')
    private String id;
    @NotEmpty()
    private String userName;
    private String avatar;//头像

    public UserCopyVo(UserEntity user) {
        this.userName = user.getUserName();
        this.id = user.getUuid();
        this.avatar = user.getAvatar();
    }

    /**
     * 设置分组
     * 继承 jakarta.validation.groups.Default 自定义验证组
     */
    public interface add extends Default {
    }

    public interface update extends Default {
    }
}
    @ApiOperation(value = 'test', notes = 'test action')
    @PostMapping('/test')
    public String test(@Validated(UserCopyVo.update.class) @RequestBody UserCopyVo u) {
        System.out.println(u);
        return 'success0000';
    }

四、@Valid拓展

有的时候,我们发现@Valid的一些注解无法满足我们的特殊开发需求,那就需要对其注解进行拓展了。
要拓展@Valid注解,可以按照以下步骤进行操作:

4.1、创建一个自定义的注解

@Constraint注解来定义这个自定义注解,并指定一个自定义的校验器类。

@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AnnotateValidator.class)
public @interface Annotate{
    String message() default "Invalid value";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

4.2、创建一个自定义的校验器类

创建自定义校验器类实现ConstraintValidator接口,并重写initializeisValid方法。

public class AnnotateValidator implements ConstraintValidator<Annotate, String> {
    @Override
    public void initialize(Annotate annotation) {
        // 初始化校验器,可以空着
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 执行自己的校验逻辑,返回布尔值即可
        return value != null && value.startsWith("custom");
    }
}

CustomValidator校验器类实现了ConstraintValidator<CustomValid, String>接口,其中CustomValid是自定义注解的类型,String是要校验的值的类型。

4.3、测试

在需要校验的字段或方法参数上使用自定义的注解。

public class Test{
    @CustomValid(message = "参数格式不对")
    private String customField;
}
public class MyController {
    @PostMapping("/myEndpoint")
    public void myEndpoint(@Valid @RequestBody Test myTest) {
        // ...
    }
}

五、工作机制

  • 注解@Valid的方法、构造函数、方法参数、成员属性,下面的约束注解是使用Hibernate validation校验机制。
  • 注解@Validated的类、方法、方法参数,下面的约束注解是使用Spring Validator校验机制使用。
  • 组合使用,请求进来的时候先通过Spring框架校验器优先对@Validated注解的地方开始校验,随后通过再Hibernate的校验器对@Valid注解的地方校验。

@Valid 和 @Validated 的约束注解都是使用的包 jakarta.validation.constraints

六、快速失败(Fail Fast)

校验机制默认会校验完所有字段,然后才抛出异常。但是,在某些情况下,我们希望出现一个校验错误就立马返回。校验错误产生的消息压栈太多也可能导致OOM。

如果想要达成这种效果,需要通过配置开启 Fali Fast 机制,一旦校验失败就立即返回。

配置@Valid验证器,只对@Valid注解的方法、构造函数、方法参数、成员属性生效

配置@Validated 验证器,只对@Validated 注解的类、方法、方法参数生效

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import jakarta.validation.Validation;
import jakarta.validation.ValidatorFactory;

@Configuration
public class ValidatorConfiguration implements WebMvcConfigurer {

    /**
     * 配置@Valid验证器
     *
     * @return jakarta.validation.Validator
     */
    @Bean
    public jakarta.validation.Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
            .configure()
            // 开启 fail fast 机制
            .failFast(true)
            .buildValidatorFactory();
        return validatorFactory.getValidator();
    }

    /**
     * 配置@Validated验证器
     *
     * @return org.springframework.validation.Validator
     */
    @Override
    public org.springframework.validation.Validator getValidator() {
        return new SpringValidatorAdapter(validator());
    }
}

七、相关规范

JSR-303

Spring Boot 项目优雅的整合JSR-303进行参数校验

JSR 是 Java Specification Requests的简称,即Java规范提案,是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。

而JSR-303 则是 JAVA EE 6 中的一项子规范,叫做 Bean Validation。

Bean Validation 为 JavaBean 验证定义了相应的元数据模型和API,它是一个运行时的数据验证框架,在验证之后,验证的错误信息马上就会被返回。

在我们的应用程序中,通过使用Bean Validation 或是自己定义的约束(constraint),例如 @NotNull、@Max等 , 就可以确保数据模型(JavaBean)的正确性,非常的方便。

需要注意的是,JSR-303虽然定义了Bean校验的标准,但并没有提供实现。而hibernate validation是对这个规范的实现(JSR-303声明了@Valid接口,而hibernate-validator对其进行了实现),并增加了校验注解,如@Email、@Length等等。

而Spring Validation则是对hibernate validation的二次封装,用来支持Spring MVC的参数自动校验。

八、@Validated 和 @Valid 注解在不同位置的作用

待完善