Validate验证总结

686 阅读7分钟
在真实的开发中,我们经常会遇到需要对数据进行校验的业务,那么本篇文章对此进行总结。暂时总结三种方法,大家可以根据需要选择使用。
一、Java Bean Validation 验证
二、SpringBoot Validate 统一处理
三、Spring Validation 校验处理

一、Java Bean Validation 验证

1.引入依赖

注意:java bean validation参数验证,在使用的时候,有时候会报错,原因是跟spring版本不兼容,这里演示的版本如下:
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.1.1.Final</version>
</dependency>
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.3.1.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

2.测试实体类

注意:将需要的校验规则直接通过注解写在实体类对应字段上即可
@Data
@AllArgsConstructor
public class Test {
    @Length(min = 1,max = 5,message = "姓名长度应该在1-5之间")
    private String name;
    @Range(min = 1,max = 100,message = "年龄应该在1-100之间")
    private Integer age;
    @DecimalMax(value = "100.00",message = "体重有些超标哦")
    @DecimalMin(value = "60.00",message = "多吃点饭吧")
    private BigDecimal weight;
    @Future(message = "元素必须是一个将来的日期")
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date date;
    @AssertTrue(message = "此值只能为true")
    private Boolean isuser;
    @Email(message = "邮箱地址无效")
    private String email;
}

3.校验工具类

注意:
(1)如果想要使用,直接调用ValidationUtils.validate参数直接传实体对象即可。
(2)validate方法可以优化,常见的是新建一个set集合,将所有的提示消息放到set中之后返回即可。
public class ValidationUtils implements Serializable {
    public static void main(String[] args) {
        Test test = new Test("张三张三张三",0,new BigDecimal(110.00),new Date(),false,"12345");
        validate(test);
    }

    private static ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

    public static <T> String validate(T t) {

        try {
            Validator validator = factory.getValidator();
            Set<ConstraintViolation<T>> constraintViolations = validator.validate(t);
            for (ConstraintViolation<T> constraintViolation : constraintViolations) {
                System.out.println("invalidvalue: "+constraintViolation.getInvalidValue() == null?"":constraintViolation.getInvalidValue().toString()+"  propertypath: "+constraintViolation.getPropertyPath()+"  message: "+constraintViolation.getMessage());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}

4.总结一波实体类上支持的注解

image.png

二、SpringBoot Validate 统一处理

1. 原理
通过继承`ResponseEntityExceptionHandler`这个类并实现`handleMethodArgumentNotValid`这个方法,就可以统一处理所以经过`Valid`注解过的接口
源码地址:Github:https://github.com/lengrongfu/validate-common-demo

1.引入依赖

<!-- 注意:里面自带了需要的校验包hibernate-validator等 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.测试实体类

@Data
public class Test {
    public Test(){}
    @Length(min = 1,max = 5,message = "姓名长度应该在1-5之间")
    private String name;
    @Range(min = 1,max = 100,message = "年龄应该在1-100之间")
    private Integer age;
    @DecimalMax(value = "100.00",message = "体重有些超标哦")
    @DecimalMin(value = "60.00",message = "多吃点饭吧")
    private BigDecimal weight;
    @Future(message = "元素必须是一个将来的日期")
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date date;
    @AssertTrue(message = "此值只能为true")
    private Boolean isuser;
    @Email(message = "邮箱地址无效")
    private String email;
}

3.配置类

public class ValidateCommonHandler extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status,WebRequest request) {
        for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
            String defaultMessage = fieldError.getDefaultMessage();
            Object value = fieldError.getRejectedValue();
            String data = "请求参数值为:"+ JSON.toJSONString(value);
            break;
        }
        return new ResponseEntity("", HttpStatus.OK);
    }
}

4.测试类

@ApiOperation("测试")
@PostMapping("/test")
public R test(@RequestBody @Valid Test test){
    return R.ok();
}

//参数Test内容如下:
{
    "name":"张三张三张三",
    "age":"0",
    "weight":"110.00",
    "date":"2020-1-1 12:11:11",
    "isuser":"false",
    "email":"12345"
}
效果如下:

image.png

5.支持注解总结

image.png

6.一些常见的应用总结

下面定义一个实体类,来大概描述每种注解的常见用法:(涉及到嵌套校验)
@Data
public class Student {

    @Length(min = 1,message = "姓名不能为空")
    @NotNull(message = "姓名不能为空")
    private String name;

    @NotNull(message = "年龄不能为空")
    @Min(value = 18,message = "年龄不能小于18岁")
    @Max(value = 56,message = "年龄不能大于56岁")
    private Integer age;

    @NotNull(message = "邮箱不能为空")
    @Email(message = "邮箱地址无效")
    private String email;

    @NotEmpty(message = "身份证号不能为空")
    @Pattern(regexp = "^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$",message = "请输入正确的身份证号")
    private String idCard;

    @NotEmpty(message = "手机号码不能为空")
    @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "请输入正确的手机号码")
    private String phone;

    @NotNull(message = "请输入合法的时间")
    @Future(message = "请输入合法的时间")
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")
    private Date inSchoolDate;

    @NotNull(message = "请输入正确的银行卡号")
    @CreditCardNumber(message = "请输入正确的银行卡号")
    private String backCard;

    //嵌套校验
    @NotEmpty(message = "请填写女朋友信息")
    private List<@Valid Girlfriend> girlfriends;

}

@Data
public class Girlfriend {

    @NotEmpty(message = "请输入姓名")
    private String name;
    @NotNull(message = "请输入年龄")
    @Range(min = 18,max = 24,message = "对不起,年龄不在合适的范围内")
    private Integer age;
    @NotNull(message = "请输入性别")
    @Digits(integer = 1,fraction = 0,message = "只接受女性为女朋友")
    private Integer gender;
}

三、Spring Validation 校验处理 *

1.背景

Java API规范(JSR303)定义了Bean校验的标准validation-api,但没有提供实现.
hibernate validation是对这个规范的实现,并增加了校验注解如@Email、@Length等.
Spring Validation是对hibernate validation的二次封装,用于支持spring mvc参数自动校验.

POST、PUT请求一般会使用requestBody传递参数,这种情况下,后端使用DTO对象进行接收.
只要给DTO对象加上@Validated注解就能实现自动参数校验.
如果校验失败,会抛出MethodArgumentNotValidException异常,Spring默认会将其转为400(Bad Request)请求.
DTO表示数据传输对象(Data Transfer Object),用于服务器和客户端之间交互传输使用的.
在spring-web项目中可以表示用于接收请求参数的Bean对象。

2.依赖

<!--引入依赖-->
<!--如果spring-boot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖。-->
<!--如果spring-boot版本大于2.3.x,则需要手动引入依赖 -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.1.Final</version>
</dependency>

3.校验处理

(1)简单校验

此处的校验注解写成Validated或者Valid都可以,不过他俩之间有一些区别:

1.分组:
    @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制,这个网上也有资料,不详述
    @Valid:作为标准JSR-303规范,还没有吸收分组的功能
2.注解位置:
    @Validated:可以用在类型方法和方法参数上但是不能用在成员属性(字段)上
    @Valid:可以用在方法构造函数方法参数和成员属性(字段)上
3.嵌套验证:
    @Valid //嵌套验证必须使用@Valid
    private List< Student> student;
4.@Valid一般用于方法入口,@Validated用于校验实体类的接口参数,用于controller上
@Data
public class Test {
    @Length(min = 1,max = 5,message = "姓名长度应该在1-5之间")
    private String name;
    @Range(min = 1,max = 100,message = "年龄应该在1-100之间")
    private Integer age;
}

@ApiOperation("测试")
@PostMapping("/test")
public R test(@RequestBody @Valid Test test){
    return R.ok();
}

@ApiOperation("测试")
@PostMapping("/test")
public R test(@RequestBody @Validated Test test){
    return R.ok();
}

{
    "name":"张三张三张三",
    "age":"0"
}
效果

image.png

(2)嵌套校验

嵌套校验一定要在要校验的对象类型的属性/list上使用@Valid注解:
@Data
public class Test {
    @Length(min = 1,max = 5,message = "姓名长度应该在1-5之间")
    private String name;
    @Range(min = 1,max = 100,message = "年龄应该在1-100之间")
    private Integer age;
    @Valid
    private Test2 test;
    @Valid
    private List<Test2> test2;
}

@Data
public class Test2 {
    @Max(value = 1)
    private Integer sex;
    @Length(min = 0,max = 2)
    private String jobname;
}

@ApiOperation("测试")
@PostMapping("/test")
public R test(@RequestBody @Validated Test test){
    return R.ok();
}

{
    "name":"张三张三张三",
    "age":"0",
    "test":{
        "sex":"11",
        "jobname":"李四李四李四"
    },
    "test2":[{
        "sex":"222",
        "jobname":"王五王五王五王五"
    },{
        "sex":"3333",
        "jobname":"赵六赵六赵六赵六"
    }]
}
效果

image.png

(3)分组校验

在实际项目中,可能多个方法需要使用同一个DTO类来接收参数,而不同方法的校验规则很可能是不一样的。
这个时候,简单地在DTO类的字段上加约束注解无法解决这个问题。因此,spring-validation支持了分组校验的功能,专门用来解决这类问题。
//这里定义四种规则
public class GroupValidator {
    public interface Arules{}
    public interface Brules{}
    public interface Crules{}
    public interface Drules{}
}

//实体类属性注解上加上分组标志
@Data
public class Test {
    @Length(min = 1,max = 5,groups = {GroupValidator.Arules.class, GroupValidator.Brules.class})
    private String name;
    @Range(min = 1,max = 100,groups = {GroupValidator.Crules.class, GroupValidator.Drules.class})
    private Integer age;
}

//参数列表里指定要校验的分组
@PostMapping("/test")
public R test(@RequestBody @Validated(GroupValidator.Arules.class) Test test){
    return R.ok();
}

//json
{
    "name":"张三张三张三",
    "age":"0"
}
效果

image.png

(4)自定义校验

//首先自定义一个注解
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {TestValidator.class})
public @interface TestAnnotation {
    // 默认错误消息
    String message() default "此值只能为1";

    // 分组
    Class<?>[] groups() default {};

    // 负载
    Class<? extends Payload>[] payload() default {};
}

//对这个注解写验证的逻辑
public class TestValidator implements ConstraintValidator<TestAnnotation, String> {
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if(!"1".equals(s)){
            return false;
        }else{
            return true;
        }
    }
}

//实体类中使用这个注解
@Data
public class Test {
    //@Length(min = 1,max = 5)
    @TestAnnotation
    private String name;
    @Range(min = 1,max = 100)
    private Integer age;
}

//验证
@PostMapping("/test")
public R test(@RequestBody @Validated Test test){
    return R.ok();
}

//传入json
{
    "name":"张三张三张三",
    "age":"0"
}
效果

image.png

参考

1.www.cnblogs.com/xiaogangfan… 2.cloud.tencent.com/developer/a… 3.lengrongfu.github.io/2018/12/27/…