参考文章
一、区别
先总结一下它们的区别:
- 来源
- @org.springframework.validation.annotation.Validated :是Spring框架特有的注解,属于Spring的一部分,也是JSR 303的一个变种。它提供了一些 @Valid 所没有的额外功能,比如分组验证。
- @jakarta.validation.Valid:Java EE提供的标准注解,它是JSR 303规范的一部分,主要用于Hibernate Validation等场景。 说明:jakarta.* 包名的旧名称是 javax.*
- 注解位置
- @Validated : 用在类、方法、方法参数上。但不能用于成员属性。
- @Valid:可以用在方法、构造函数、方法参数、成员属性上。
- 分组
- @Validated :支持分组验证,可以更细致地控制验证过程。此外,由于它是Spring专有的,因此可以更好地与Spring的其他功能(如Spring的依赖注入)集成。
- @Valid:主要支持标准的Bean验证功能,不支持分组验证。
- 嵌套验证
- @Validated :不支持嵌套验证。
- @Valid:支持嵌套验证,可以嵌套验证对象内部的属性。
- @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
接口,并重写initialize
和isValid
方法。
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 注解在不同位置的作用
待完善