一、案例对象
本文章会以案例为主,讲解@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)
@Valid
private String name;
/*** 年龄*/
@NotNull(message = "请输入年龄!")
@Range(message = "年龄范围为 {min} 到 {max} 岁之间!", min = 1, max = 100)
@Valid
private 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 方便一些,它可以帮我们节省一定的代码量,并且使得方法看上去更加的简洁。
如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、评论、收藏➕关注,您的支持是我坚持写作最大的动力。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/161444.html