一、案例对象

本文章会以案例为主,讲解@Valid@Validated这两个注解的区别与用法。

1.首先,创建一个学生对象,如下:

import lombok.Data;/*** 学生对象*/
@Data
public class Student {/*** 姓名*/private String name;/*** 年龄*/private Integer age;/*** 性别*/private Integer sex;/*** 手机号*/private String phone;}

这里我使用Lombok注解,省去了构造方法、get/set方法。

2.PersonController里有一个,新增学生的方法 addStudent():

import com.st.microservice.usercenter.infrastructure.entity.Student;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 人员controller*/
@RestController
@RequestMapping("/person")
public class PersonController {@PostMapping("/addStudent")public String addStudent(Student student) {// TODO 学生信息入库return "新增学生成功";}}

现有个需求:学生的姓名不能为空,且长度不能超过10个字符;如果,在不用注解的情况下,我们大致会这样写:

/*** 人员controller*/
@RestController
@RequestMapping("/person")
public class PersonController {@PostMapping("/addStudent")public String addStudent(Student student) {String name = student.getName();if (name == null || name.trim().length() == 0) {return "学生姓名不能为空";}if (name.trim().length() > 10) {return "学生姓名不得超过10个字符";}// TODO 学生信息入库return "新增学生成功";}}

用Apifox测试一下:

(1)正常情况

(2)姓名为空情况

(3)姓名超长情况

可以看到,结果是没什么问题的。 那么,这时候又来了新的需求:学生的年龄是必填项,且年龄范围在1~100岁之间。我们只需要再加一个判断语句就好:

/*** 人员controller*/
@RestController
@RequestMapping("/web/omp/person")
public class PersonController {@PostMapping("/addStudent")public String addStudent(@RequestBody Student student) {String name = student.getName();if (name == null || name.trim().length() == 0) {return "学生姓名不能为空";}if (name.trim().length() > 10) {return "学生姓名不得超过10个字符";}Integer age = student.getAge();if (age == null) {return "学生年龄不能为空";}if (age < 1 || age > 100) {return "学生年龄不能小于1岁或大于100岁";}// TODO 学生信息入库return "新增学生成功";}}

这也是没问题的,但是有一个问题。我们只校验了2个字段,就写了10多行的校验代码,要是校验更多的字段,岂不是要写更多的代码?通常来说,当一个方法中的无效业务代码量过多时,往往代码设计存在问题。

那么如何解决呢?首先大家应该会想到将对应的验证过程抽成一个验证方法,如下:

// 学生对象 属性校验
private String verify(Student student) {String name = student.getName();if (name == null || name.trim().length() == 0) {return "学生姓名不能为空";}if (name.trim().length() > 10) {return "学生姓名不得超过10个字符";}Integer age = student.getAge();if (age == null) {return "学生年龄不能为空";}if (age < 1 || age > 100) {return "学生年龄不能小于1岁或大于100岁";}return null;}
    @PostMapping("/addStudent")public String addStudent(@RequestBody Student student) {String result = verify(student);if (result != null) {return result;}// TODO 学生信息入库return "新增学生成功";}

但这种方式只是抽了一个方法,虽然业务方法看起来清爽了很多,但实际的代码量并没有下降,有种换汤不换药的感觉。

二、@Valid注解

此时,我们可以使用 Spring 中的 @valid 注解,具体如下:

1、首先,pom文件中要加入@valid依赖:(如果你是 springboot 项目,可以不用引入了,它已经存在于最核心的 web 开发包里面)

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.0.5.RELEASE</version>
</dependency>

如果你不是 springboot 项目,那么引入下面依赖即可:

<dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>1.1.0.Final</version>
</dependency><dependency><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>5.4.1.Final</version>
</dependency>

2、可以优化代码了,在 Student 类的属性上加如下注解:

/*** 学生对象*/
@Data
public class Student {/*** 姓名*/@NotBlank(message = "请输入姓名")@Length(message = "名称不能超过个 {max} 字符", max = 10)private String name;/*** 年龄*/@NotNull(message = "请输入年龄")@Range(message = "年龄范围为 {min} 到 {max} 岁之间", min = 1, max = 100)private Integer age;/*** 性别*/@NotNull(message = "请选择性别")private Integer sex;/*** 手机号*/private String phone;}

既然验证,那么就肯定会有验证结果,所以我们需要用一个东西来存放验证结果,做法也很简单,在参数直接添加一个BindingResult,具体如下:

import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.validation.Valid;/*** 人员controller*/
@RestController
@RequestMapping("/web/omp/person")
public class PersonController {@PostMapping("/addStudent")public String addStudent(@RequestBody @Valid Student student, BindingResult bindingResult) {// 所有字段是否验证通过,true-数据有误,false-数据无误if (bindingResult.hasErrors()){// 有误,则返回前端第一条错误信息return bindingResult.getAllErrors().get(0).getDefaultMessage();}// TODO 学生信息入库return "新增学生成功";}}

因为测试结果和之前的一样,这里就不展示了。 这样代码不但简洁了很多,想要的校验也有了,  真的是yyds。

一些项目中常用的字段属性校验注解,可以参考我的这片文章:常用注解

三、@Validated注解

简单来说,@Validated注解是@Valid注解的一个升级版。

我们可以看到,在使用 @Valid 进行验证时,需要用一个对象去接收校验结果,最后根据校验结果判断,从而提示用户。

当我们把校验逻辑注释掉后,再次执行上面的请求后。

可以看到我们的程序继续往后执行了。

现在,我们去掉方法参数上的 @Valid 注解和其配对的 BindingResult 对象,

然后再校验的对象前面添加上 @Validated 注解。

这个时候,我们再次请求,可以看到,我们的程序报异常了。

那么,从这里我们可以得知,当我们的数据存在校验不通过的时候,程序就会抛出

org.springframework.validation.BindException 的异常。

在实际开发的过程中,我们肯定不能讲异常直接展示给用户,而是给能看懂的提示。

于是,我们不妨可以通过捕获异常的方式,将该异常进行捕获。

首先我们创建一个校验异常捕获类 ValidExceptionHandler ,然后加上 @RestControllerAdvice 注解,该注解表示他会去抓所有 @Controller 标记类的异常,并在异常处理后返回以 JSON 或字符串的格式响应前端。

在异常捕捉到后,我们同上面的 @valid 校验一样,只返回第一个错误提示。

直接上代码:

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;import java.util.List;/*** 捕捉异常*/
@Slf4j
@ControllerAdvice
public class ValidatedExceptionHandler {/*** 处理@Validated参数校验失败异常*/@ResponseBody@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(MethodArgumentNotValidException.class)public Result<String> exceptionHandler(MethodArgumentNotValidException exception) {BindingResult result = exception.getBindingResult();StringBuilder stringBuilder = new StringBuilder();if (result.hasErrors()) {List<ObjectError> errors = result.getAllErrors();errors.forEach(p -> {FieldError fieldError = (FieldError) p;log.warn("Bad Request Parameters: dto entity [{}],field [{}],message [{}]", fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());stringBuilder.append(fieldError.getDefaultMessage());});}return Result.error(stringBuilder.toString());}}

重启项目,再次请求,发现不报错了,校验成功。

四、嵌套参数校验和分组参数校验

  • 嵌套参数验证(验证实体中的其他需要被验证的对象集合或其他对象)

(1)实体类

import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;/*** 学生对象*/
@Data
public class Student {/*** 姓名*/@NotBlank(message = "请输入姓名!")@Length(message = "名称不能超过个 {max} 字符!", max = 10)@Validprivate String name;/*** 年龄*/@NotNull(message = "请输入年龄!")@Range(message = "年龄范围为 {min} 到 {max} 岁之间!", min = 1, max = 100)@Validprivate Integer age;/*** 性别*/@NotNull(message = "请选择性别!")private Integer sex;/*** 手机号*/private String phone;}

(2)控制类

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 人员controller*/
@RestController
@RequestMapping("/web/omp/person")
public class PersonController {@PostMapping("/addStudent")public Result<String> addStudent(@Validated @RequestBody Student student) {// TODO 学生信息入库return Result.success("新增学生成功");}}

a.校验通过

b.校验不通过

  • 分组参数验证(将不同的校验规则分给不同的组,在使用时,指定不同的校验规则)

a.创建两个接口类

/*** 分组校验1*/
public interface Group1 {
}
/*** 分组校验2*/
public interface Group2 {
}

b.实体类

import lombok.Data;
import org.hibernate.validator.constraints.Length;import javax.validation.constraints.*;/*** 学生对象2*/
@Data
public class StudentDto {/*** 姓名*/@NotBlank(message = "请输入姓名!", groups = {Group1.class})@Length(max = 10, message = "名称不能超过个10字符!")private String name;/*** 年龄*/@NotNull(message = "请输入年龄!")@Min(value = 1, message = "年龄不得小于1岁!", groups = {Group1.class})@Max(value = 120, message = "年龄不得大于120岁!", groups = {Group2.class})private Integer age;/*** 性别*/@NotNull(message = "请选择性别")private Integer sex;/*** 手机号*/@Pattern(regexp = "^(13[0-9]|14[579]|15[0-3,5-9]|16[6]|17[0135678]|18[0-9]|19[89])\\d{8}$", message = "手机号码有误!", groups = {Group2.class})private String phone;/*** 邮箱*/@Email(message = "邮箱有误!", groups = {Group2.class})private String email;}

c.控制类

import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 人员controller*/
@RestController
@RequestMapping("/web/omp/person")
public class PersonController {// 未分组校验@PostMapping("/add1")public Result<String> add1(@Validated @RequestBody StudentDto dto) {// TODO 学生信息入库return Result.success(dto);}// 按Group1规则校验@PostMapping("/add2")public Result<String> add2(@Validated(Group1.class) @RequestBody StudentDto dto) {// TODO 学生信息入库return Result.success(dto);}// 按Group2规则校验@PostMapping("/add3")public Result<String> add3(@Validated(Group2.class) @RequestBody StudentDto dto) {// TODO 学生信息入库return Result.success(dto);}}

d.测试结果:

1.未分组校验通过

2. 未分组校验未通过

3.Group1分组校验通过

4.Group1分组校验未通过

5.Group2分组校验通过

6.Group2分组校验未通过

7.使用默认分组

ps:将控制层的add3()方法做以下调整:

    // 按默认分组规则校验@PostMapping("/add3")public Result<String> add3(@Validated(Default.class) @RequestBody StudentDto dto) {// TODO 学生信息入库return Result.success(dto);}

Default.class为Validated依赖中含有的接口类,非自定义接口类 

  • 默认分组,参数校验通过

  • 默认分组,参数校验未通过

五、@Valid 和 @Validated 区别

先看下两者的源码:

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {
}
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {Class<?>[] value() default {};
}
  • 相同点:

@Valid 和 @Validated 两者都可以对数据进行校验,在校验字段上加上规则注解(@NotNull, @NotEmpty等)都可以对 @Valid 和 @Validated 生效。

  • 不同点:

@Valid 进行校验的时候,需要用 BindingResult 来做一个校验结果接收。当校验不通过的时候,如果手动不 return ,则并不会阻止程序的执行;

@Valid:没有分组的功能;

@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上;

@Validated 进行校验的时候,当校验不通过的时候,程序会抛出400异常,阻止方法中的代码执行,这时需要再写一个全局校验异常捕获处理类,然后返回校验提示

@Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制;

@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上;

两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。

总体来说,@Validated 使用起来要比 @Valid 方便一些,它可以帮我们节省一定的代码量,并且使得方法看上去更加的简洁。

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、评论、收藏➕关注,您的支持是我坚持写作最大的动力。

校验注解:@Valid 和 @Validated区别与用法(附详细案例)相关推荐

  1. @Validated注解详解,分组校验,嵌套校验,@Valid和@Validated 区别,Spring Boot @Validated

    技术栈: spring boot 2.3.3.RELEASE hibernate-validator 文末附项目源代码 目录 简述 项目依赖 全局异常处理类 基础参数校验 实体类 控制类 测试 嵌套参 ...

  2. @Valid与@Validated区别

    1.@Valid与@Validated作用 @Valid与@Validated都是用来校验接收参数的.@Valid是使用Hibernate validation的时候使用@Validated是只用Sp ...

  3. mingw64下动态库和静态库链接的真正区别和用法(详细)

    笔者一直以来都对mingw64下动态库和静态库链接的真正区别和用法存疑,于是做了一些测试,这篇文章记录了测试过程和测试结果,如果只想知道结果可以跳转到文章末尾 一.准备工作 首先准备三个测试文件 其中 ...

  4. 客户端访问https时应无浏览器(含终端)安全警告信息;_https和http有什么区别(内附详细分析)...

    很多站长知道https和http有所不同,但是究竟两者有什么不同浑然不知,针对这种情况,本文Seo星火给大家详细分析一下https和http有什么区别. 一.基本概念: (http服务器-->本 ...

  5. https和http有什么区别(内附详细分析)

    很多站长知道https和http有所不同,但是究竟两者有什么不同浑然不知,针对这种情况,本文给大家详细分析一下https和http有什么区别. 一.基本概念(http服务器–>本地浏览器,正确快 ...

  6. 因子分析模型(主成分解)、及与主成分分析模型的联系与区别(附详细案例)

    * * *  * *   * * * 因子分析是主成分分析的推广和发展,它也是多元统计分析中将为的一种方法. 因子分析是研究相关阵和或协方差阵的内部依赖关系,它将多个变量综合为少数几个因子,以再现原始 ...

  7. @Valid和@Validated

    简介 @Validation是一套帮助我们继续对传输的参数进行数据校验的注解,通过配置Validation可以很轻松的完成对数据的约束,配合BindingResult可以直接提供参数验证结果 所有参数 ...

  8. 常用校验注解@NotEmpty,@NotBlank,@NotNull,@Valid,@Validated用法区别,以及搭配 BindingResult使用,嵌套验证等《使用|CSDN创作打卡》

    文章目录 一.常用注解@NotEmpty,@NotBlank,@NotNull介绍 二.@Valid与@Validated的使用与区别 2.1@Valid与@Validated区别: 2.2嵌套验证 ...

  9. notnull注解_参数校验注解Validated和Valid的区别,这次终于有人说清楚了

    Spring Validation验证框架对参数的验证机制提供了@Validated(Spring's JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR- ...

最新文章

  1. Linux篇---ftp服务器的搭建
  2. Tensorflow基础入门十大操作总结
  3. 在asp.net2.0中使用串行化对象实现自定义配置
  4. WindowManager如何被Android深度解析(2)
  5. IPv6套接字编程介绍
  6. 方式四:修改模块导入段来拦截API
  7. js读取div从html中导入,在html中div+css布局的简单应用...-js+div+css下拉导航菜单完整代...-CSS文件的条件导入 - Gene Li_169IT.COM...
  8. 基于springboot2.x集成缓存注解及设置过期时间
  9. oracle数据modeling分类,由浅入深 NoSQL的五种主流数据模型
  10. mysql客户端查询_MySQL数据库之利用mysql客户端查询UCSC数据库
  11. 用c语言求最大公约数的流程图,如何用c语言求最大公约数和最小公倍数
  12. [.net 面向对象程序设计进阶] (2) 正则表达式 (一) 快速入门
  13. python写前端和js_Python【13】【前端编程】- JS基础
  14. sql 从ip列表中查询ip段_IP地址段查询深度优化案例
  15. 11 种方法教你用 Python 高效下载资源
  16. paip.2013年技术趋势以及热点 v3.0 cao
  17. 一篇文章带你彻底了解Kubernetes
  18. Are We Ready For Learned Cardinality Estimation实验环境搭建
  19. 太原用计算机单位的工资,太原个税计算器_太原税后月薪|工资计算器_太原个人所得税查询 - Tax518...
  20. 基于ARM的嵌入式SMTP远程控制设计

热门文章

  1. 基于FaceNet的实时人脸识别训练
  2. 【基础】Flink -- DataStream API
  3. python键盘键值表_Python怎么记录键盘鼠标敲击次数|Python统计鼠标点击次数 - PS下...
  4. 大数据学习方法,学习大数据需要的基础和路线
  5. 图构建:领域本体设计原则与动态本体
  6. python+win32com分割多页ppt为单页多文件
  7. 51单片机 (四)延时函数
  8. 哔咔漫画怎样切换横屏?
  9. Android Studio 布局 - ScrollView和HorizontalScrollView
  10. Security Best Practices+Klocwork