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

一、案例对象

        本文章会以案例为主,讲解@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 方便一些,它可以帮我们节省一定的代码量,并且使得方法看上去更加的简洁。

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

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: @Valid注解和@Validated注解都是用于校验数据的注解,但是它们的使用场景略有不同。 @Valid注解是JSR-303规范中定义的注解,用于校验JavaBean中的属性值是否符合规范。它可以用在方法参数、方法返回值、方法参数中的属性、方法返回值中的属性等位置上。在Spring Boot中,我们可以在Controller中使用@Valid注解校验请求参数是否符合规范。 @Validated注解Spring框架中提供的注解,它可以用于校验方法参数是否符合规范。它支持分组校验,可以根据不同的场景对参数进行不同的校验。在Spring Boot中,我们可以在Service层或者Controller中使用@Validated注解校验方法参数是否符合规范。 总的来说,@Valid注解用于校验JavaBean中的属性值,而@Validated注解用于校验方法参数。两者都可以用于校验数据,但是使用场景略有不同。 ### 回答2: @Valid注解和@Validated注解都用于校验数据的有效性,但是它们有所不同。 @Valid注解是来自于javax.validation规范中的注解,是用于Bean Validation校验的,它可以用于校验Java Bean对象中的字段。@Valid注解只能加在某个目标类型参数上,并且该参数类型需要是一个对象,它可以递归验证对象中的所有字段和对象。 @Validated注解则是Spring框架提供的注解,是用于Spring校验的,它可以用于校验方法和参数。@Validated注解用于验证参数和返回值,通常放在接口方法上。@Validated注解描述的约束条件必须是JSR303支持的约束条件的JavaBean,而@Valid注解不支持约束条件。 总的来说,@Valid注解是制定了JSR303规范而来的,属于Bean Validation更多的是参数校验,而@Validated注解Spring框架专门提供的注解,用于控制层的校验,其作用与@Valid注解类似,但更加灵活,能进行更多的分组约束、级联验证等。 ### 回答3: @Valid注解和@Validated注解Java中用于数据校验的两个注解。它们都是基于Bean Validation规范的,提供了对Java Bean属性进行校验的功能。 @Valid注解用于进行简单的数据校验,它可以在Controller层的方法参数中对参数进行校验。它的作用是告诉Spring框架对该参数进行数据校验,如果校验失败,则会抛出ConstraintViolationException异常。该注解使用时需要配合bindingResult参数一起使用,bindingResult参数用于接收校验结果。 @Validated注解更加强大,除了提供了@Valid注解的功能外,它还可以在Service层和Dao层对方法参数进行校验。它支持Spring Expression Language(SpEL)表达式,并提供了基于分组校验的功能。基于分组校验可以根据不同的校验场景,对同一个实体进行不同的校验,增强了数据校验的灵活性。 除了以上的差异,还有一个重要区别:@Valid注解是标准的Java注解,而@Validated注解Spring提供的注解。因此,如果项目中只使用了Spring框架,那么可以直接使用@Validated注解。但如果使用的是标准的Java环境,则需要使用@Valid注解

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Javaの甘乃迪

感谢看客老爷的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值