别再混淆了!一文带你搞懂@Valid和@Validated的区别
上篇文章我们简单介绍和使用了一下Springboot的参数校验,同时也用到了 @Valid 注解和 @Validated 注解,那它们之间有什么不同呢?
区别
先总结一下它们的区别:
- 来源
- @Validated :是Spring框架特有的注解,属于Spring的一部分,也是JSR 303的一个变种。它提供了一些 @Valid 所没有的额外功能,比如分组验证。
- @Valid:Java EE提供的标准注解,它是JSR 303规范的一部分,主要用于Hibernate Validation等场景。
- 注解位置
- @Validated : 用在类、方法和方法参数上,但不能用于成员属性。
- @Valid:可以用在方法、构造函数、方法参数和成员属性上。
- 分组
- @Validated :支持分组验证,可以更细致地控制验证过程。此外,由于它是Spring专有的,因此可以更好地与Spring的其他功能(如Spring的依赖注入)集成。
- @Valid:主要支持标准的Bean验证功能,不支持分组验证。
- 嵌套验证
- @Validated :不支持嵌套验证。
- @Valid:支持嵌套验证,可以嵌套验证对象内部的属性。
这些理论性的东西没什么好说的,记住就行。我们主要看分组和嵌套验证是什么,它们怎么用。
实操阶段
话不多说,通过代码来看一下分组和嵌套验证。
为了提示友好,修改一下全局异常处理类:
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 参数校检异常
* @param e
* @return
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseResult handle(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
StringJoiner joiner = new StringJoiner(";");
for (ObjectError error : bindingResult.getAllErrors()) {
String code = error.getCode();
String[] codes = error.getCodes();
String property = codes[1];
property = property.replace(code ,"").replaceFirst(".","");
String defaultMessage = error.getDefaultMessage();
joiner.add(property+defaultMessage);
}
return handleException(joiner.toString());
}
private ResponseResult handleException(String msg) {
ResponseResult result = new ResponseResult<>();
result.setMessage(msg);
result.setCode(500);
return result;
}
}
分组校验
分组验证是为了在不同的验证场景下能够对对象的属性进行灵活地验证,从而提高验证的精细度和适用性。一般我们在对同一个对象进行保存或修改时,会使用同一个类作为入参。那么在创建时,就不需要校验id,更新时则需要校验用户id,这个时候就需要用到分组校验了。
对于定义分组有两点要特别注意:
- 定义分组必须使用接口。
- 要校验字段上必须加上分组,分组只对指定分组生效,不加分组不校验。
有这样一个需求,在创建用户时校验用户名,修改用户时校验用户id。下面对我们对这个需求进行一个简单的实现。
- 创建分组
CreationGr0up 用于创建时指定的分组:
public interface CreationGr0up {
}
UpdateGr0up 用于更新时指定的分组:
public interface UpdateGr0up {
}
- 创建用户类
创建一个UserBean用户类,分别校验 username
字段不能为空和id
字段必须大于0,然后加上CreationGr0up
和 UpdateGr0up
分组。
/**
* @author 公众号-索码理(suncodernote)
*/
@Data
public class UserBean {
@NotEmpty( groups = {CreationGr0up.class})
private String username;
@Min(value = 18)
private Integer age;
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 1 ,groups = {UpdateGr0up.class})
private Long id;
}
- 创建接口
在ValidationController 中新建两个接口 updateUser
和 createUser
:
@RestController
@RequestMapping("validation")
public class ValidationController {
@GetMapping("updateUser")
public UserBean updateUser(@Validated({UpdateGr0up.class}) UserBean userBean){
return userBean;
}
@GetMapping("createUser")
public UserBean createUser(@Validated({CreationGr0up.class}) UserBean userBean){
return userBean;
}
}
- 测试
先对 createUser
接口进行测试,我们将id的值设置为0,也就是不满足id必须大于0的条件,同样 username 不传值,即不满足 username 不能为空的条件。 通过测试结果我们可以看到,虽然id没有满足条件,但是并没有提示,只提示了username不能为空。
再对 updateUser
接口进行测试,条件和测试 createUser
接口的条件一样,再看测试结果,和 createUser
接口测试结果完全相反,只提示了id最小不能小于1。
至此,分组功能就演示完毕了。
嵌套校验
介绍嵌套校验之前先看一下两个概念:
- 嵌套校验(Nested Validation) 指的是在验证对象时,对对象内部包含的其他对象进行递归验证的过程。当一个对象中包含另一个对象作为属性,并且需要对这个被包含的对象也进行验证时,就需要进行嵌套校验。
- 嵌套属性指的是在一个对象中包含另一个对象作为其属性的情况。换句话说,当一个对象的属性本身又是一个对象,那么这些被包含的对象就可以称为嵌套属性。
有这样一个需求,在保存用户时,用户地址必须要填写。下面来简单看下示例:
- 创建地址类 AddressBean
在AddressBean 设置 country
和city
两个属性为必填项。
@Data
public class AddressBean {
@NotBlank
private String country;
@NotBlank
private String city;
}
- 修改用户类,将AddressBean作为用户类的一个嵌套属性
特别提示:想要嵌套校验生效,必须在嵌套属性上加 @Valid
注解。
@Data
public class UserBean {
@NotEmpty(groups = {CreationGr0up.class})
private String username;
@Min(value = 18)
private Integer age;
private String email;
@Min(value = 1 ,groups = {UpdateGr0up.class})
private Long id;
//嵌套验证必须要加上@Valid
@Valid
@NotNull
private AddressBean address;
}
- 创建一个嵌套校验测试接口
@PostMapping("nestValid")
public UserBean nestValid(@Validated @RequestBody UserBean userBean){
System.out.println(userBean);
return userBean;
}
- 测试
我们在传参时,只传 country
字段,通过响应结果可以看到提示了city
字段不能为空。
可以看到使用了 @Valid
注解来对 Address 对象进行验证,这会触发对其中的 Address 对象的验证。通过这种方式,可以确保嵌套属性内部的对象也能够参与到整体对象的验证过程中,从而提高验证的完整性和准确性。
总结
本文介绍了@Valid
注解和@Validated
注解的不同,同时也进一步介绍了Springboot 参数校验的使用。不管是 JSR-303、JSR-380又或是 Hibernate Validator ,它们提供的参数校验注解都是有限的,实际工作中这些注解可能是不够用的,这个时候就需要我们自定义参数校验了。下篇文章将介绍一下如何自定义一个参数校验器。
来源:juejin.cn/post/7344958089429434406