【深度思考】如何优雅的校验参数?

news2024/11/16 20:27:47

在日常的开发工作中,为了保证落库数据的完整性,参数校验绝对是必不可少的一部分,本篇文章就来讲解下在项目中该如何优雅的校验参数。

假设有一个新增学员的接口,一般第一步我们都会先校验学员信息是否正确,然后才会落库,简单起见,假设新增学员时只有2个字段:姓名、年龄。

@Data
public class StudentVO {
    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;
}

要求为:姓名和年龄必填,姓名不能超过20个字符。

1. 最原始的写法

先来看下最原始的写法,相信大多数人都这么写过,或者说在初学Java时都这么写过:

public String validateStudentVO(StudentVO studentVO) {
    if (StringUtils.isBlank(studentVO.getName())) {
        return "姓名不能为空";
    }
    if (studentVO.getName().length() > 20) {
        return "姓名不能超过20个字符";
    }
    if (studentVO.getAge() == null) {
        return "年龄不能为空";
    }

    return null;
}

这么写最好理解,但一般一个项目中都会有很多接口,如果都这么写的话,重复代码会非常多,显得非常臃肿,而且对于一个工作多年的开发来说,如果每天都写这样的代码,会觉得特别没有技术含量。

2. Bean Validation

既然有需求场景,就会有规范,这个规范就是Bean Validation,官网地址是 https://beanvalidation.org/。

Bean Validation先后经历了1.0(JSR 303)、1.1(JSR 349)、2.0(JSR 380)这3个版本,目前项目中使用比较多的是Bean Validation 2.0,本篇文章讲解的内容也是基于Bean Validation 2.0版本。

Bean Validation 2.0之后,现在改名叫Jakarta Bean Validation了。

pom依赖坐标如下所示:

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

不过从2.0.1.Final之后的版本依赖都改为了jakarta.validation-api:

新版本pom依赖坐标如下所示:

<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>2.0.2</version>
</dependency>

3. Hibernate Validator

Hibernate Validator是 Bean Validation 的参考实现 ,不仅提供了规范中所有内置constraint的实现,除此之外还提供了一些附加的 constraint。

pom依赖坐标如下所示:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.2.5.Final</version>
</dependency>

因为hibernate-validator中已经包含了validation-api,因此项目中如果引入了hibernate-validator,就没必要重复引入validation-api了:

image-20221221144308018

4. Bean Validation 2.0原生注解

Bean Validation 2.0中包含了22个注解,如下图所示:

接下来详细讲解下这22个注解的用途。

4.1 @AssertTrue

作用:被标记的元素必须为true。

支持的Java类型:boolean、Boolean。

使用示例:

@AssertTrue
private Boolean newStudent;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setNewStudent(false);

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

snipaste_20221219_162307

上面输出的message是默认的,在实际使用时可以自定义:

@AssertTrue(message = "newStudent必须为true")
private Boolean newStudent;

效果如下图所示:

snipaste_20221219_164946

注意事项:

1)@AssertTrue注解识别不了字段值为null的场景:

snipaste_20221219_163148

2)如果将@AssertTrue注解使用在boolean、Boolean之外的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常:

@AssertTrue
private String name;

4.2 @AssertFalse

作用:被标记的元素值必须为false。

其余的和@AssertTrue注解一致。

使用示例:

@AssertFalse(message = "newStudent必须为false")
private Boolean newStudent;

4.3 @DecimalMax

作用:被标记的元素必须小于或等于指定的值。

支持的Java类型:BigDecimal、BigInteger、byte、Byte、short、Short、int、Integer、long、Long、String。

使用示例:

@DecimalMax(value = "30000")
private BigDecimal balance;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setBalance(new BigDecimal("30001"));

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

上面输出的message是默认的,在实际使用时可以自定义:

@DecimalMax(value = "30000", message = "账户余额必须小于或等于30000")
private BigDecimal balance;

效果如下图所示:

snipaste_20221219_180912

注意事项:

1)@DecimalMax注解识别不了字段值为null的场景:

2)如果将@DecimalMax注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常:

@DecimalMax(value = "30000", message = "账户余额必须小于或等于30000")
private Boolean newStudent;

4.4 @DecimalMin

作用:被标记的元素值必须大于或等于指定的值。

其余的和@DecimalMax注解一致。

使用示例:

@DecimalMin(value = "5000", message = "充值余额必须大于或等于5000")
private BigDecimal rechargeAmount;

4.5 @Digits

作用:被标记的元素整数位数和小数位数必须小于或等于指定的值。

支持的Java类型:BigDecimal、BigInteger、byte、Byte、short、Short、int、Integer、long、Long、String。

使用示例:

@Digits(integer = 6, fraction = 2)
private BigDecimal rechargeAmount;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setRechargeAmount(new BigDecimal("100000.999"));

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

上面输出的message是默认的,在实际使用时可以自定义:

@Digits(integer = 6, fraction = 2, message = "充值金额只允许6位整数、2位小数")
private BigDecimal rechargeAmount;

效果如下图所示:

注意事项:

1)@Digits注解识别不了字段值为null的场景:

2)如果将@Digits注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常:

@Digits(integer = 6, fraction = 2, message = "充值金额只允许6位整数、2位小数")
private Boolean newStudent;

4.6 @Email

作用:被标记的元素必须是邮箱地址。

支持的Java类型:String。

使用示例:

@Email
private String email;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setEmail("活着");

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220140414799

上面输出的message是默认的,在实际使用时可以自定义:

@Email(message = "无效的电子邮件地址")
private String email;

效果如下图所示:

image-20221220141623447

注意事项:

1)@Email注解识别不了字段值为null或空字符串""的场景:

image-20221220142325648

2)如果将@Email注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4.7 @Future

作用:被标记的元素必须为当前时间之后。

支持的Java类型:Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime等。

使用示例:

@Future
private Date startingDate;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setStartingDate(new Date());

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220143841736

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@Future(message = "必须是一个将来的时间")
private Date startingDate;

2)@Future注解识别不了字段值为null的场景。

3)如果将@Future注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4.8 @FutureOrPresent

作用:被标记的元素必须为当前时间或之后。

支持的Java类型:Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime等。

使用示例:

@FutureOrPresent
private Date startingDate;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setStartingDate(DateUtils.addMilliseconds(new Date(), 1));

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220145520752

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@FutureOrPresent(message = "必须是一个将来或现在的时间")
private Date startingDate;

2)@FutureOrPresent注解识别不了字段值为null的场景。

3)如果将@FutureOrPresent注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4.9 @Past

作用:被标记的元素必须为当前时间之前。

支持的Java类型:Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime等。

使用示例:

@Past
private Date latestAttendanceTime;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setLatestAttendanceTime(DateUtils.addMinutes(new Date(), 10));

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220150626760

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@Past(message = "必须是一个过去的时间")
private Date latestAttendanceTime;

2)@Past注解识别不了字段值为null的场景。

3)如果将@Past注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4.10 @PastOrPresent

作用:被标记的元素必须为当前时间或之前。

支持的Java类型:Date、Calendar、Instant、LocalDate、LocalDateTime、LocalTime等。

使用示例:

@PastOrPresent
private Date latestAttendanceTime;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setLatestAttendanceTime(DateUtils.addMinutes(new Date(), 10));

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220151459339

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@PastOrPresent(message = "必须是一个过去或现在的时间")
private Date latestAttendanceTime;

2)@PastOrPresent注解识别不了字段值为null的场景。

3)如果将@PastOrPresent注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4.11 @Max

作用:被标记的元素必须小于或等于指定的值。

支持的Java类型:BigDecimal、BigInteger、byte、Byte、short、Short、int、Integer、long、Long、String。

使用示例:

@Max(value = 10000)
private BigDecimal balance;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setBalance(new BigDecimal("10000.01"));

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220152359301

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@Max(value = 10000, message = "必须小于或等于10000")
private BigDecimal balance;

2)@Max注解识别不了字段值为null的场景。

3)如果将@Max注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4.12 @Min

作用:被标记的元素必须大于或等于指定的值。

支持的Java类型:BigDecimal、BigInteger、byte、Byte、short、Short、int、Integer、long、Long、String。

使用示例:

@Min(value = 5000)
private BigDecimal rechargeAmount;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setRechargeAmount(new BigDecimal("4999"));

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220155849229

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@Min(value = 5000, message = "必须大于或等于5000")
private BigDecimal rechargeAmount;

2)@Min注解识别不了字段值为null的场景。

3)如果将@Min注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4.13 @Negative

作用:被标记的元素必须是负数。

支持的Java类型:BigDecimal、BigInteger、byte、Byte、short、Short、int、Integer、long、Long、float、Float、

double、Double。

使用示例:

@Negative
private BigDecimal rechargeAmount;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setRechargeAmount(new BigDecimal("0"));

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220171024124

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@Negative(message = "金额必须是负数")
private BigDecimal rechargeAmount;

2)@Negative注解识别不了字段值为null的场景。

3)如果将@Negative注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4.14 @NegativeOrZero

@NegativeOrZero注解和@Negative注解基本一致,唯一的区别是被标记的元素除了可以是负数,也可以是零。

使用示例:

@NegativeOrZero(message = "金额必须是负数或零")
private BigDecimal rechargeAmount;

4.15 @Positive

作用:被标记的元素必须是正数。

支持的Java类型:BigDecimal、BigInteger、byte、Byte、short、Short、int、Integer、long、Long、float、Float、

double、Double。

使用示例:

@Positive
private BigDecimal rechargeAmount;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setRechargeAmount(new BigDecimal("0"));

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220173146103

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@Positive(message = "充值金额必须是正数")
private BigDecimal rechargeAmount;

2)@Positive注解识别不了字段值为null的场景。

3)如果将@Positive注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4.16 @PositiveOrZero

@PositiveOrZero注解和@Positive注解基本一致,唯一的区别是被标记的元素除了可以是正数,也可以是零。

使用示例:

@PositiveOrZero(message = "充值金额必须是正数或零")
private BigDecimal rechargeAmount;

4.17 @Null

作用:被标记的元素必须为null。

支持的Java类型:Object。

使用示例:

@Null
private String namePinYin;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setNamePinYin("zhangsan");

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220174738949

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@Null(message = "姓名拼音必须为null")
private String namePinYin;

4.18 @NotNull

作用:被标记的元素必须不为null。

其余和@Null注解一致。

4.19 @NotEmpty

作用:被标记的元素不为null,且不为空(字符串的话,就是length要大于0,集合的话,就是size要大于0)。

支持的Java类型:String、Collection、Map、Array。

使用示例:

/**
 * 姓名
 */
@NotEmpty
private String name;

/**
 * 家长信息
 */
@NotEmpty
private List<ParentVO> parentVOList;

ParentVO如下所示:

@Data
public class ParentVO {
    /**
     * 姓名
     */
    @NotEmpty(message = "姓名不能为空")
    private String name;

    /**
     * 手机号
     */
    @NotEmpty(message = "手机号不能为空")
    private String mobile;
}

验证:

StudentVO studentVO = new StudentVO();
studentVO.setName("");
studentVO.setParentVOList(new ArrayList<>());

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220181939012

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@NotEmpty(message = "姓名不能为空")
private String name;

2)如果将@NotEmpty注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

3)嵌套验证问题

简单修改下上面的验证代码:

StudentVO studentVO = new StudentVO();
studentVO.setName("张三");

ParentVO parentVO = new ParentVO();
studentVO.setParentVOList(Lists.newArrayList(parentVO));

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

此时的输出结果如下所示:

image-20221220183222130

从输出结果可以看出,StudentVO里增加的@NotEmpty注解生效了,但嵌套的ParentVO里的校验注解并未生效,如果想生效的话,需要加上@Valid注解:

/**
 * 家长信息
 */
@Valid
@NotEmpty
private List<ParentVO> parentVOList;

再次执行上面的验证代码,输出结果如下图所示:

image-20221220183913587

可以看出,嵌套的ParentVO里的校验注解也生效了。

4.20 @NotBlank

作用:被标记的元素不为null,且必须有一个非空格字符。

这里提下和@NotEmpty的区别,

作用于字符串的话,@NotEmpty能校验出null、”“这2种场景,而@NotBlank能校验出null、”“、” “这3种场景,

作用于集合的话,@NotEmpty支持,但@NotBlank不支持。

支持的Java类型:String。

使用示例:

@NotBlank
private String name;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setName(" ");

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221220185653073

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@NotBlank(message = "姓名不能为空")
private String name;

2)如果将@NotBlank注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4.21 @Size

作用:被标记的元素长度/大小必须在指定的范围内(字符串的话,就是length要在指定的范围内,集合的话,就是size要在指定的范围内)。

支持的Java类型:String、Collection、Map、Array。

使用示例:

@Size(min = 2, max = 5)
private String name;

@Size(min = 1, max = 5)
private List<ParentVO> parentVOList;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setName("张三李四王五");
studentVO.setParentVOList(new ArrayList<>());

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221221103331170

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@Size(min = 2, max = 5, message = "姓名不能少于2个字符,不能多于5个字符")
private String name;

@Size(min = 1, max = 5, message = "至少添加一位家长信息,最多不能超过5位")
private List<ParentVO> parentVOList;

2)@Size注解识别不了字段值为null的场景。

2)如果将@Size注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4.22 @Pattern

作用:被标记的元素必须匹配指定的正则表达式。

支持的Java类型:String。

使用示例:

@Pattern(regexp = "^[1-9]\\d{5}$")
private String postcode;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setPostcode("2000001");

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221221105001625

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@Pattern(regexp = "^[1-9]\\d{5}$", message = "邮政编码格式错误")
private String postcode;

2)@Pattern注解识别不了字段值为null的场景。

3)如果将@Pattern注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

5. Hibernate Validator扩展注解

Hibernate Validator除了支持上面提到的22个原生注解外,还扩展了一些注解:

image-20221221145450366

接下来详细讲解几个常用的。

5.1 @Length

作用:被标记的元素必须在指定的长度范围内。

支持的Java类型:String。

使用示例:

@Length(min = 2, max = 5)
private String name;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setName("张三李四王五");

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221221110257679

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@Length(min = 2, max = 5, message = "姓名不能少于2个字符,不能多于5个字符")
private String name;

2)@Length注解识别不了字段值为null的场景。

3)如果将@Length注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

5.2 @Range

@Range注解相当于同时融合了@Min注解和@Max注解的功能,如下图所示:

image-20221221113851165

因此它的作用是:被注解的元素必须大于或等于指定的最小值,小于或等于指定的最大值。

它支持的Java类型也和@Min注解和@Max注解一致:

BigDecimal、BigInteger、byte、Byte、short、Short、int、Integer、long、Long、String。

使用示例:

@Range(min = 1000L, max = 10000L)
private BigDecimal rechargeAmount;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setRechargeAmount(new BigDecimal("500"));

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221221112728704

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@Range(min = 1000L, max = 10000L, message = "至少充值1000,最多充值10000")
private BigDecimal rechargeAmount;

2)@Range注解识别不了字段值为null的场景。

3)如果将@Range注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

4)不建议将@Range注解使用在String类型上。

5.3 @URL

作用:被标记的元素必须是一个有效的url地址。

它的内部其实是使用了@Pattern注解,如下图所示:

image-20221221115137250

因此它支持的Java类型和@Pattern注解一致:String。

使用示例:

@URL
private String url;

验证:

StudentVO studentVO = new StudentVO();
studentVO.setRechargeAmount(new BigDecimal("1000"));
studentVO.setUrl("url地址");

ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
Validator validator = validatorFactory.getValidator();

Set<ConstraintViolation<StudentVO>> constraintViolations = validator.validate(studentVO);
for (ConstraintViolation<StudentVO> constraintViolation : constraintViolations) {
    System.out.println(constraintViolation.getMessage());
}

输出结果:

image-20221221114343660

注意事项:

1)上面输出的message是默认的,在实际使用时可以自定义:

@URL(message = "无效的url地址")
private String url;

2)@URL注解识别不了字段值为null的场景。

3)如果将@URL注解使用在不支持的Java类型,程序会抛出javax.validation.UnexpectedTypeException异常。

6. Spring Web项目

如果项目本身是基于Spring Web的,可以使用@ControllerAdvice+@ExceptionHandler来全局处理参数校验。

首先,新建一个全局异常处理器,并添加@RestControllerAdvice注解:

@RestControllerAdvice
public class GlobalExceptionHandler {
    
}

说明:因为接口返回的是json,这里使用@RestControllerAdvice等价于同时使用了@ControllerAdvice@ResponseBody

接着,我们将文初的StudentVO修改为:

import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class StudentVO {
    /**
     * 姓名
     */
    @NotBlank(message = "姓名不能为空")
    @Length(max = 20, message = "姓名不能超过20个字符")
    private String name;

    /**
     * 年龄
     */
    @NotNull(message = "年龄不能为空")
    private Integer age;
}

然后在api接口的参数前增加@Valid注解:

@RestController
public class StudentController {
    @Autowired
    private StudentService studentService;

    @PostMapping("student/add")
    public CommonResponse<Void> add(@RequestBody @Valid StudentVO studentVO) {
        studentService.add(studentVO);

        return CommonResponse.success();
    }
}

6.1 处理MethodArgumentNotValidException异常

在全局异常处理器中添加MethodArgumentNotValidException异常处理逻辑:

/**
 * 处理MethodArgumentNotValidException
 *
 * @param e
 * @return
 */
@ExceptionHandler(MethodArgumentNotValidException.class)
public CommonResponse<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
    log.error("方法参数不正确", e);

    return CommonResponse.error(HttpStatus.BAD_REQUEST.value(),
            "参数错误:" + e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
}

最后使用postman调用接口进行验证,如下图所示:

snipaste_20221111_144615

从接口返回结果,可以看出,全局异常处理器成功的处理了MethodArgumentNotValidException异常的逻辑,因为上面调用接口,其实程序是抛出了org.springframework.web.bind.MethodArgumentNotValidException异常,不过因为在全局异常处理器中定义了该异常的处理逻辑,所以程序按照定义的格式返回给了前端,而不是直接将异常抛给前端:

snipaste_20221111_145918

6.2 处理HttpMessageNotReadableException异常

上面的接口,如果我们不传参数,程序会抛出org.springframework.http.converter.HttpMessageNotReadableException异常,如下图所示:

snipaste_20221111_151244

因此需要在全局异常处理器中添加HttpMessageNotReadableException异常处理逻辑:

/**
 * 处理HttpMessageNotReadableException
 *
 * @param e
 * @return
 */
@ExceptionHandler(HttpMessageNotReadableException.class)
public CommonResponse<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
    log.error("参数错误", e);

    return CommonResponse.error(HttpStatus.BAD_REQUEST.value(), "参数错误");
}

使用postman调用接口进行验证,如下图所示:

snipaste_20221111_151748

6.3 处理MissingServletRequestParameterException异常

假设我们有一个根据名字查询学员的GET请求的接口:

@GetMapping("student/get")
public CommonResponse<StudentVO> get(@RequestParam String name) {
    StudentVO studentVO = studentService.getByName(name);

    return CommonResponse.success(studentVO);
}

但调用时,我们不传递参数name,程序会抛出org.springframework.web.bind.MissingServletRequestParameterException异常,如下图所示:

snipaste_20221111_164107

因此需要在全局异常处理器中添加MissingServletRequestParameterException异常处理逻辑:

/**
 * 处理MissingServletRequestParameterException
 *
 * @param e
 * @return
 */
@ExceptionHandler(MissingServletRequestParameterException.class)
public CommonResponse<Void> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
    log.error("参数错误", e);

    return CommonResponse.error(HttpStatus.BAD_REQUEST.value(), "参数错误");
}

使用postman调用接口进行验证,如下图所示:

snipaste_20221111_164216

6.4 处理ConstraintViolationException异常

还是上面的查询学员接口,不仅要传参数name,还得保证参数name不能是个空字符串,因此需要在参数前加上@NotBlank注解:

@GetMapping("student/get")
public CommonResponse<StudentVO> get(@RequestParam @NotBlank(message = "名字不能为空") String name) {
    StudentVO studentVO = studentService.getByName(name);

    return CommonResponse.success(studentVO);
}

并且需要在控制器Controller上添加@Validated注解:

snipaste_20221111_165935

注意事项:控制器上的@Validated注解一定要添加,否则参数上加的@NotBlank注解不会生效。

此时调用接口,但参数name传递个空字符串,程序会抛出javax.validation.ConstraintViolationException异常,如下图所示:

snipaste_20221111_165205

因此需要在全局异常处理器中添加ConstraintViolationException异常处理逻辑:

/**
 * 处理ConstraintViolationException
 *
 * @param e
 * @return
 */
@ExceptionHandler(ConstraintViolationException.class)
public CommonResponse<Void> handleConstraintViolationException(ConstraintViolationException e) {
    log.error("参数错误", e);

    return CommonResponse.error(HttpStatus.BAD_REQUEST.value(), e.getConstraintViolations().iterator().next().getMessage());
}

使用postman调用接口进行验证,如下图所示:

snipaste_20221111_170610

6.5 扩展

全局异常处理器除了处理上面提到的4个参数校验的异常,一般也会处理业务上抛出的异常,如Service层抛出的自定义异常:

@Service
public class StudentService {
    public StudentVO getByName(String name) {
        throw new ServiceException("学员不存在");
    }
}
/**
 * 业务异常
 */
public class ServiceException extends RuntimeException {
    public ServiceException(String message) {
        super(message);
    }
}

所以一般全局异常处理器中都有处理ServiceException的逻辑:

/**
 * 处理ServiceException
 *
 * @param e
 * @return
 */
@ExceptionHandler(ServiceException.class)
public CommonResponse<Void> handleServiceException(ServiceException e) {
    log.error("业务异常", e);

    return CommonResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
}

因为异常有很多种类型,而本文中提到的只是其中的几个,因此为了起到兜底作用,可以在全局异常处理器中添加处理Exception异常的逻辑,当程序抛出未知的异常时,可以统一处理,返回某个固定的提示给前端:

/**
 * 处理Exception
 *
 * @param e
 * @return
 */
@ExceptionHandler(Exception.class)
public CommonResponse<Void> handleException(Exception e) {
    log.error("系统异常", e);

    return CommonResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "操作失败,请稍后重试");
}

6.6 完整的GlobalExceptionHandler代码

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolationException;

/**
 * 全局异常处理器
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 处理MethodArgumentNotValidException
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResponse<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("方法参数不正确", e);

        return CommonResponse.error(HttpStatus.BAD_REQUEST.value(),
                "参数错误:" + e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
    }

    /**
     * 处理HttpMessageNotReadableException
     *
     * @param e
     * @return
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public CommonResponse<Void> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        log.error("参数错误", e);

        return CommonResponse.error(HttpStatus.BAD_REQUEST.value(), "参数错误");
    }

    /**
     * 处理MissingServletRequestParameterException
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public CommonResponse<Void> handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        log.error("参数错误", e);

        return CommonResponse.error(HttpStatus.BAD_REQUEST.value(), "参数错误");
    }

    /**
     * 处理ConstraintViolationException
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public CommonResponse<Void> handleConstraintViolationException(ConstraintViolationException e) {
        log.error("参数错误", e);

        return CommonResponse.error(HttpStatus.BAD_REQUEST.value(), e.getConstraintViolations().iterator().next().getMessage());
    }

    /**
     * 处理ServiceException
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ServiceException.class)
    public CommonResponse<Void> handleServiceException(ServiceException e) {
        log.error("业务异常", e);

        return CommonResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
    }

    /**
     * 处理Exception
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public CommonResponse<Void> handleException(Exception e) {
        log.error("系统异常", e);

        return CommonResponse.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "操作失败,请稍后重试");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/107873.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么硬盘在macbook上无法编辑?mac不能往移动硬盘拷东西

为什么硬盘在macbook上无法编辑&#xff1f;如果您只想在Mac上查看NTFS文件&#xff0c;只需将NTFS 外置存储设备连接到mac电脑并查看文件。但要编辑或传输文件&#xff0c;则需要NTFS工具。 NTFS文件格式与Mac不兼容&#xff0c;但许多用户仍然喜欢使用NTFS文件&#xff0c;而…

Xshell及Xftp安装配置实现对Linux的远程登录

为什么要远程登录&#xff1f; Linux服务器是小组共享的 正式上线的项目是运行在公网上的 具体的结构图如下图所示&#xff1a; 准备工作 注意&#xff0c;商业用途的Xshell和Xftp是需要收费的&#xff0c;因此&#xff0c;我们下载免费的个人版本即可。 登录官网&#x…

:沉寂6个月,高薪不再是梦,这一次,我赢了......

前言 六个月对于人的一生来说是极其短暂的&#xff0c;有的人迷迷糊糊、浑浑噩噩的就过去了&#xff0c;有的人让其虚度在游戏中&#xff0c;有的人在不喜欢的岗位上日复一日。 但&#xff0c;总有一部分人会选择用来提升自己、或是探索未知领域掌握新的技能&#xff0c;而我…

Promise难懂?一篇文章让你轻松驾驭

前言 前端js学习中&#xff0c;让大家最难受的就是异步的问题&#xff0c;解决异步、回调地狱等问题时你必须得学会promise&#xff0c;对于多数前端程序员来说promise简直就是噩梦&#xff0c;本篇文章就是从通俗易懂的角度做为切入点&#xff0c;帮助大家轻松掌握promise 异步…

如何使用报表工具FastReport VCL 设计器中的 PDF/A?

Fastreport是目前世界上主流的图表控件&#xff0c;具有超高性价比&#xff0c;以更具成本优势的价格&#xff0c;便能提供功能齐全的报表解决方案&#xff0c;连续三年蝉联全球文档创建组件和库的“ Top 50 Publishers”奖。 慧都科技是Fast Reports在中国区十余年的友好合作…

0基础学python容易吗?零基础学习路线目标

零基础Python学习路线及阶段学习目标&#xff0c;首先应该夯实Python核心基础、Web前端编程、Django开发框架、Flask开发框架、爬虫与数据分析等知识&#xff0c;理解机器学习相关的基本概念及系统处理流程。 零基础Python学习路线及阶段学习目标&#xff1a; 阶段一、Python核…

【React学习】React更新渲染原理

当我们调用 setState 之后发生了什么&#xff1f;react经历了怎样的过程将新的 state 渲染到页面上&#xff1f; 一次react更新&#xff0c;核心就是对虚拟dom进行diff&#xff0c;找出最少的需要变化的dom节点&#xff0c;然后对其进行相应的dom操作&#xff0c;用户即可在页…

亲子游小程序开发,助力商家掌握亲子流量

随着新生代父母越来越年轻化&#xff0c;育儿观念上也发生了很大改变&#xff0c;当代父母已经不在单纯的关注孩子的学习情况&#xff0c;学习固然重要但是孩子的身心健康也是非常重要的。所以很多父母开始带着自己的孩子参加各种各样的亲子活动&#xff0c;多参加亲子活动可以…

Python数据分析之Pandas库

Python数据分析之Pandas库一、Pandas简介二、Pandas库的安装三、Pandas的数据结构四、Series 和 DataFrame 数据结构的使用五、其他可以参考的网站一、Pandas简介 Pandas是python的一个数据分析包&#xff0c;最初由AQR Capital Management于2008年4月开发&#xff0c;并于200…

Struts2框架中的Action接口和ActionSupport类

Struts2框架中的Action接口和ActionSupport类1、Action接口2、ActionSupport类3、登录案例3.1、页面3.2、控制器3.3、struts.xml配置3.4、测试1、Action接口 Action是Struts2框架的核心&#xff0c;因为它们适用于任何MVC&#xff08;Model View Controller&#xff09;框架。…

【Maven基础】单一架构案例(二)

第三节 搭建环境&#xff1a;事务控制 1、总体思路 2、TransactionFilter 2.1、创建 Filter 类 2.2、TransactionFilter 完整代码 public class TransactionFilter implements Filter {// 声明集合保存静态资源扩展名private static Set<String> staticResourceExtNam…

长时间序列遥感数据处理及在全球变化、物候提取、植被变绿与固碳分析、生物量估算与趋势分析等领域中的实践技术应用

【查看原文】长时间序列遥感数据处理及在全球变化、物候提取、植被变绿与固碳分析、生物量估算与趋势分析等领域中的实践技术应用 植被是陆地生态系统中最重要的组分之一&#xff0c;也是对气候变化最敏感的组分&#xff0c;其在全球变化过程中起着重要作用&#xff0c;能够指…

云知声: 基于 JuiceFS 的超算平台存储实践

云知声从一家专注于语音及语言处理的技术公司&#xff0c;现在技术栈已经发展到具备图像、自然语言处理、信号等全栈式的 AI 能力&#xff0c;是国内头部人工智能独角兽企业。公司拥抱云计算&#xff0c;在智慧医疗、智慧酒店、智慧教育等方面都有相应的解决方案。 Atlas 是云知…

技术栈入门------RabbitMQ

Direct交换机是路由键精准匹配 Fanout交换机是不看路由键 &#xff0c;只要你消息发给了某个交换机&#xff0c;这个交换机就立马把消息转给绑定了这个交换机的所有队列&#xff0c;所以速度最快 Topic交换机可以把一个消息根据交换机和消息队列的绑定的路由键进行匹配&#xf…

没有二十年功力,写不出Thread.sleep(0)这一行“看似无用”的代码!

你好呀&#xff0c;我是喜提七天居家隔离的歪歪。 这篇文章要从一个奇怪的注释说起&#xff0c;就是下面这张图&#xff1a; 我们可以不用管具体的代码逻辑&#xff0c;只是单单看这个 for 循环。 在循环里面&#xff0c;专门有个变量 j&#xff0c;来记录当前循环次数。 第…

项目实战之旅游网(一)项目介绍 项目搭建

目录 一.项目介绍 1.功能介绍 2.技术选型 3.实体类 二.项目搭建 1.创建项目 2.adminLTE 3.编写后端首页 4.提取统一后台模板 5.重构后台首页 ************************************************************************* 项目代码地址&#xff1a;等写完这个项目我…

jsoup

1.什么是jsoup jsoup&#xff1a;Java HTML解析器&#xff0c;专为HTML编辑&#xff0c;清理&#xff0c;抓取和XSS安全而构建 2.依赖 <dependency><!-- jsoup HTML parser library https://jsoup.org/ --><groupId>org.jsoup</groupId><artifac…

干货!深入学习必学的模型微调

学习目标 知道微调的原理能够利用微调模型来完成图像的分类任务1.微调 如何在只有6万张图像的MNIST训练数据集上训练模型。学术界当下使用最广泛的大规模图像数据集ImageNet&#xff0c;它有超过1,000万的图像和1,000类的物体。然而&#xff0c;我们平常接触到数据集的规模通…

浅析JWT

Cookie-session 我们都知道JWT一般用于用户登录等需要记住的操作&#xff0c;在谈论JWT之前就不得不谈谈以前的cookie-session登录了 。因为http协议是一种无状态协议&#xff0c;即每次服务端接收到客户端的请求时&#xff0c;都是一个全新的请求&#xff0c;服务器并不知道客…

【从零开始学微服务】08.引入微服务架构的时机

大家好&#xff0c;欢迎来到万猫学社&#xff0c;跟我一起学&#xff0c;你也能成为微服务专家。 在了解引入微服务架构的时机之前&#xff0c;架构设计时一般需要遵循的三个原则。 架构设计三个原则 架构设计一般需要遵循以下三个原则&#xff1a; 合适原则&#xff1a;合适…