Spring Validation
1. Spring Validation
JSR-303 简介
JSR-303 是 JavaEE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是 Hibernate Validator
。
此实现与 Hibernate ORM 没有任何关系。JSR-303 用于对 Java Bean 中的字段的值进行验证。 Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中使用注解的方式对表单提交的数据方便地验证。Spring 4.0 开始支持 Bean Validation 功能。在日常开发中,Hibernate Validator
经常用来验证bean的字段,基于注解,方便快捷高效。
2. JSR-303 基本的校验规则
2.1空检查
@Null
验证对象是否为null
@NotNull
验证对象是否不为null
, 无法查检长度为 0 的字符串@NotBlank
检查约束字符串是不是Null
还有被Trim
的长度是否大于 0,只对字符串,且会去掉前后空格@NotEmpty
检查约束元素是否为NULL
或者是EMPTY
使用hibernate validator出现上面的错误, 需要注意@NotNull 和 @NotEmpty 和@NotBlank 区别
@NotEmpty 用在集合类上面
@NotBlank 用在String上面@NotNull 用在基本类型上
在枚举类上不要加非空注解
2.2 布尔检查
@AssertTrue
验证Boolean
对象是否为true
@AssertFalse
验证Boolean
对象是否为false
2.3 长度检查
@Size(min=, max=)
验证对象(Array, Collection , Map, String)
长度是否在给定的范围之内@Length(min=, max=)
验证字符串长度介于 min 和 max 之间
2.4 日期检查
@Past
验证Date
和Calendar
对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期@Future
验证Date
和Calendar
对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期
2.5 正则检查
@Pattern
验证String
对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式regexp
:正则表达式flags
:指定Pattern.Flag
的数组,表示正则表达式的相关选项
2.6 数值检查
注意: 建议使用在 String
,Integer
类型,不建议使用在int
类型上,因为表单值为 “”
时无法转换为 int
,但可以转换为 String
为 “”
,Integer
为 null
@Min
验证 Number 和 String 对象是否大等于指定的值@Max
验证 Number 和 String 对象是否小等于指定的值@DecimalMax
被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过 BigDecimal 定义的最大值的字符串表示.小数
存在精度@DecimalMin
被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过 BigDecimal 定义的最小值的字符串表示.小数
存在精度@Digits
验证Number
和String
的构成是否合法@Digits(integer=,fraction=)
验证字符串是否是符合指定格式的数字,integer
指定整数精度,fraction
指定小数精度@Range(min=, max=)
被指定的元素必须在合适的范围内@Range(min=10000,max=50000,message=”range.bean.wage”)
@CreditCardNumber
信用卡验证@Email
验证是否是邮件地址,如果为 null,不进行验证,算通过验证@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
@Range(min = 1, max = 4, message = "统筹类型不合法", groups = {ValidGroup.Insert.class, ValidGroup.Update.class})
@NotNull(message = "统筹类型不能为空!", groups = {ValidGroup.Insert.class, ValidGroup.Update.class})
private Integer overallType;
@Digits(integer = 1, fraction = 2, message = "统筹比例格式不正确!", groups = {ValidGroup.Insert.class, ValidGroup.Update.class})
@DecimalMin(message = "请填写0-1的比例!", value = "0.00", groups = {ValidGroup.Insert.class, ValidGroup.Update.class})
@DecimalMax(message = "请填写0-1的比例!",value = "1.00", groups = {ValidGroup.Insert.class, ValidGroup.Update.class})
@NotNull(message = "统筹比例不能为空!", groups = {ValidGroup.Insert.class, ValidGroup.Update.class})
private BigDecimal overallProportion;
1、@Digits:digit是数位的意思,这里的integer意思整数最多有几位,fraction意思小数最多有几位,只是划定了传入参数的范围,前端传值的时候可以用字符串(只要字符串里面都是数字就行)、数字;
2、@DecimalMin:decimal的最小值,传入参数必须大于等于value里面的值;
3、@NotNull:传入参数不为空,对于BigDecimal格式,用@NotNull注解,如果传参是空字符串或者只有空格的字符串,也无法通过校验。如果字段的类型是String,建议使用@NotBlank注解。
2.7 对象校验
@Valid
被注释的元素是一个对象,需要检查此对象的所有字段值@Validated
被注解的元素是一个对象或者一个类,需要检查此对象的所有字段值
3 使用 Spring Validation 验证
POM
如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。
如果spring-boot版本大于2.3.x,则需要手动引入依赖:
这里我们使用spring-boot:2.2.2.RELEASE
,Hibernate Validator :5.x
来实现Spring Validation
接口,pom.xml 文件如下:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.3.4.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
主要是增加了 org.hibernate:hibernate-validator
依赖
实体类参数校验
User
import lombok.Data;
import org.hibernate.validator.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
//使用Spring Validator和Hibernate Validator这两套Validator来进行方便的参数校验!
//这两套Validator依赖包已经包含在前面所说的web依赖包里了,所以可以直接使用。所以可以直接使用
@Data
public class User {
@NotNull(message = "用户名id不能为空")
private Long id;
@NotNull(message = "用户账号不能为空")
@Size(min = 6, max = 11, message = "账号长度必须是6-11个字符")
private String account;
@NotNull(message = "用户密码不能为空")
@Size(min = 6, max = 11, message = "密码长度必须是6-16个字符")
private String password;
//@Email使用的是org.hibernate包下的注解
// javax下的包会报错,坑
@NotNull(message = "用户邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
}
controller
实体类上加上@Valid注解,使用json传参,加@RequestBody解析json参数映射为实体类,如果校验错误不会执行业务逻辑,抛出异常
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/addUser")
//@Valid 被注释的元素是一个对象,需要检查此对象的所有字段值
//@Validated 被注解的元素是一个对象或者一个类,需要检查此对象的所有字段值
//public String addUser(@RequestBody @Valid User user,BindingResult bindingResult) {
public String addUser(@RequestBody @Valid User user) { //BindingResult bindingResult
// 如果出现异常,后面的代码不会执行
// 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里
// for (ObjectError error : bindingResult.getAllErrors()) {
// return error.getDefaultMessage();
// }
return userService.save(user);
}
}
service
public interface UserService {
String save(User user);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public String save(User user) {
System.out.println("保存成功---->"+user);
return "success";
}
}
PostMan测试
发送post
请求
{
"id": 1,
"account": "12345678",
"password": "12312123",
"email": "15096021" # 错误格式
}
响应数据
数据不友好,使用全局异常捕获返回友好提示。
{
"timestamp": "2020-09-19T06:00:38.546+00:00",
"status": 400,
"error": "Bad Request",
"trace": "org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public java.util.List cn.zysheep.springboot_interface.controller.UserController.addUser(cn.zysheep.springboot_interface.entity.User): [Field error in object 'user' on field 'email': rejected value [15096021m]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@3796b0b3,.*]; default message [邮箱格式不正确]] \r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:139)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:652)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:733)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\r\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.lang.Thread.run(Thread.java:748)\r\n",
"message": "Validation failed for object='user'. Error count: 1",
"errors": [
{
"codes": [
"Email.user.email",
"Email.email",
"Email.java.lang.String",
"Email"
],
"arguments": [
{
"codes": [
"user.email",
"email"
],
"arguments": null,
"defaultMessage": "email",
"code": "email"
},
[],
{
"arguments": null,
"defaultMessage": ".*",
"codes": [
".*"
]
}
],
"defaultMessage": "邮箱格式不正确",
"objectName": "user",
"field": "email",
"rejectedValue": "15096021m",
"bindingFailure": false,
"code": "Email"
}
],
"path": "/addUser"
}
全局处理异常
全局处理异常,处理
@RequestBody
参数校验异常,统一返回格式自定义
@RestControllerAdvice
public class ExceptionControllerAdvice {
/**
* 处理 @RequestBody参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, Object> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 获取所有异常
List<String> errors = e.getBindingResult()
.getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
body.put("errors", errors);
return body;
}
}
PostMan测试,响应数据
# 请求数据
{
"id": 1,
"account": "12345678",
"password": "12312123",
"email": "15096021m"
}
# 全局异常处理后的响应数据
{
"timestamp": "2020-09-19 14:07:05",
"errors": [
"邮箱格式不正确"
]
}
键值对Map错误信息
@RestControllerAdvice
public class ExceptionControllerAdvice {
/**
* 处理 @RequestBody参数校验异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Map<String, Object> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 获取所有异常
HashMap<String, String> map = new HashMap<>();
((MethodArgumentNotValidException) e).getBindingResult()
.getFieldErrors()
//.map(DefaultMessageSourceResolvable::getDefaultMessage)
.forEach(x->{
map.put(x.getField(),x.getDefaultMessage());
});
//.collect(Collectors.toMap(FieldError::getField, DefaultMessageSourceResolvable::getDefaultMessage));
body.put("errors", map);
return body;
}
}
表单传参校验
使用表单传参,即不使用@RequestBody
,跟上面的第一类异常捕获的异常类型不同而已。
@Controller
public class LoginController {
@PostMapping("/test1")
@ResponseBody
public AjaxResult test1(@Validated User user){
System.out.println(user);
return user;
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultVO<Map<String, Object>> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
// 从异常对象中拿到ObjectError对象
//ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 然后提取错误提示信息进行返回
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
// 获取所有异常
List<String> errors = e.getBindingResult()
.getFieldErrors()
.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
body.put("errors", errors);
return new ResultVO<>(ResultCode.FAILED,body);
}
}
单个参数校验
直接在参数前加上校验注解
@RestController
@Validated
public class UserController {
@GetMapping("/test3")
public AjaxResult test3(@NotNull(message = "name不能为空") String name ,@Email(message ="邮箱格式不正确") String email){
System.out.println(name);
System.out.println(email);
return AjaxResult.success(name+" "+email);
}
}
注意:需要在类上添加@Validated注解,否则不会校验。
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理所有参数校验时抛出的异常
* @param ex
* @return
*/
@ExceptionHandler(value = ValidationException.class)
public AjaxResult handleBindException(ValidationException ex) {
AjaxResult body = new AjaxResult();
body.put("timestamp", new Date());
// 获取所有异常
List<String> errors = new LinkedList<>();
if (ex instanceof ConstraintViolationException) {
ConstraintViolationException exs = (ConstraintViolationException) ex;
Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
for (ConstraintViolation<?> item : violations) {
errors.add(item.getMessage());
}
}
body.put("errors", errors);
body.put("code",900);
body.put("msg","提交的数据校验失败");
return body;
}
}
@Validated和@Valid的区别
一般情况下,若不需要分组校验的话:使用@Valid和@Validated并无特殊差异。
1、@Valid:@Valid注解时javax包下的注解,是jdk给提供的。标准JSR-303规范的标记型注解
2、@Validated:@Validated是@Valid 的一次封装,是Spring提供的校验机制使用。Spring的注解。是标准JSR-303的一个变种(补充),提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。
3、@Validated只能用在类、方法和参数上,而@Valid可用于方法、字段、构造器和参数上。两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能,显然@Valid注解可以提供嵌套校验的功能,@Validated除了没有嵌套校验的功能,其他@Valid注解有的功能,它都有,@Valid注解没有的功能,它也有
分组校验
在实际开发中经常会遇到这种情况:添加用户时,id是由后端生成的,不需要校验id是否为空,但是修改用户时就需要校验id是否为空。如果在接收参数的User实体类的id属性上添加NotNull,显然无法实现。这时候就可以定义分组,在需要校验id的时候校验,不需要的时候不校验。
1、定义分组
/**
*分组校验 (配合spring的@Validated功能分组使用)
*/
public class ValidGroup {
// 新增使用
public interface Insert{}
// 更新使用
public interface Update{}
// 删除使用
public interface Delete{}
// 属性必须有这两个分组的才验证
@GroupSequence({Insert.class, Update.class,Delete.class})
public interface All{}
}
2、 实体类中属性validation注解使用上面定义的组
在每个注解中有一个属性groups
//只能在Delete和Update的时候才能够进行生效.
@Min(value = 1,message = "ID不能小于1",groups = {ValidGroup.Delete.class,ValidGroup.Update.class})
private int id;
@NotBlank(message = "用户名不能为空",groups = {ValidGroup.Update.class,ValidGroup.Insert.class})
private String username;
@NotBlank(message = "密码不能为空",groups = {ValidGroup.Update.class,ValidGroup.Insert.class})
@Length(min = 6,max = 20,message = "密码长度在6-20之间")
private String password;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不合理")
private String email;
3、controller中使用@Validated指定使用哪个组
@RequestMapping("/saveUserInfo")
public UserInfo saveUserInfo(@Validated() UserInfo userInfo){
//save userInfo:将userInfo进行保存
//userInfoService.save(userInfo);
return userInfo;
}
@RequestMapping("/updateUserInfo")
public UserInfo updateUserInfo(@Validated({ValidGroup.Update.class}) UserInfo userInfo){
//save userInfo:将userInfo进行保存
//userInfoService.update(userInfo);
return userInfo;
}
@RequestMapping("/deleteUserInfo")
public UserInfo deleteUserInfo(@Validated({ValidGroup.Delete.class}) UserInfo userInfo){
//save userInfo:将userInfo进行保存
//userInfoService.delete(userInfo);
return userInfo;
}
嵌套校验
public class Item {
@NotNull(message = "id不能为空")
@Min(value = 1, message = "id必须为正整数")
private Long id;
@NotNull(message = "props不能为空")
@Size(min = 1, message = "至少要有一个属性")
private List<Prop> props;
}
public class Prop {
@NotNull(message = "pid不能为空")
@Min(value = 1, message = "pid必须为正整数")
private Long pid;
@NotNull(message = "vid不能为空")
@Min(value = 1, message = "vid必须为正整数")
private Long vid;
@NotBlank(message = "pidName不能为空")
private String pidName;
@NotBlank(message = "vidName不能为空")
private String vidName;
}
现在我们有个ItemController接受一个Item的入参,想要对Item进行验证,如下所示:
@RestController
public class ItemController {
@RequestMapping("/item/add")
public void addItem(@Validated Item item, BindingResult bindingResult) {
doSomething();
}
}
在上图中,如果Item实体的props属性不额外加注释,只有@NotNull和@Size,无论入参采用@Validated还是@Valid验证,Spring Validation框架只会对Item的id和props做非空和数量验证,不会对props字段里的Prop实体进行字段验证,也就是@Validated和@Valid加在方法参数前,都不会自动对参数进行嵌套验证。也就是说如果传的List中有Prop的pid为空或者是负数,入参验证不会检测出来。
为了能够进行嵌套验证,必须手动在Item实体的props字段上明确指出这个字段里面的实体也要进行验证。由于@Validated不能用在成员属性(字段)上,但是@Valid能加在成员属性(字段)上,而且@Valid类注解上也说明了它支持嵌套验证功能,那么我们能够推断出:@Valid加在方法参数时并不能够自动进行嵌套验证,而是用在需要嵌套验证类的相应字段上,来配合方法参数上@Validated或@Valid来进行嵌套验证。
我们修改Item类如下所示:
public class Item {
@NotNull(message = "id不能为空")
@Min(value = 1, message = "id必须为正整数")
private Long id;
@Valid // 嵌套验证必须用@Valid
@NotNull(message = "props不能为空")
@Size(min = 1, message = "props至少要有一个自定义属性")
private List<Prop> props;
}
然后我们在ItemController的addItem函数上再使用@Validated或者@Valid,就能对Item的入参进行嵌套验证。此时Item里面的props如果含有Prop的相应字段为空的情况,Spring Validation框架就会检测出来,bindingResult就会记录相应的错误。
总结一下@Validated和@Valid在嵌套验证功能上的区别:
@Validated:用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
@Valid:用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。