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

假定有一个新增学员的接口,一般第一步咱们都会先校验学员信息是否正确,然后才会落库,简单起见,假定新增学员时只有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,官网地址是 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了:

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

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());
}

输出成果:

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

上面输出的message是默许的,在实际运用时能够自界说:

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

效果如下图所示:

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

注意事项:

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

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

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;

效果如下图所示:

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

注意事项:

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());
}

输出成果:

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

上面输出的message是默许的,在实际运用时能够自界说:

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

效果如下图所示:

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

注意事项:

1)@Email注解辨认不了字段值为null或空字符串””的场景:

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

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());
}

输出成果:

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

注意事项:

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());
}

输出成果:

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

注意事项:

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());
}

输出成果:

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

注意事项:

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());
}

输出成果:

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

注意事项:

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());
}

输出成果:

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

注意事项:

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());
}

输出成果:

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

注意事项:

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());
}

输出成果:

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

注意事项:

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());
}

输出成果:

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

注意事项:

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());
}

输出成果:

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

注意事项:

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());
}

输出成果:

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

注意事项:

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());
}

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

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

从输出成果能够看出,StudentVO里增加的@NotEmpty注解收效了,但嵌套的ParentVO里的校验注解并未收效,假如想收效的话,需要加上@Valid注解:

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

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

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

能够看出,嵌套的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());
}

输出成果:

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

注意事项:

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());
}

输出成果:

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

注意事项:

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());
}

输出成果:

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

注意事项:

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个原生注解外,还扩展了一些注解:

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

接下来具体解说几个常用的。

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());
}

输出成果:

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

注意事项:

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注解的功用,如下图所示:

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

因而它的效果是:被注解的元素有必要大于或等于指定的最小值,小于或等于指定的最大值。

它支撑的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());
}

输出成果:

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

注意事项:

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注解,如下图所示:

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

因而它支撑的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());
}

输出成果:

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

注意事项:

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调用接口进行验证,如下图所示:

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

从接口回来成果,能够看出,大局反常处理器成功的处理了MethodArgumentNotValidException反常的逻辑,由于上面调用接口,其实程序是抛出了org.springframework.web.bind.MethodArgumentNotValidException反常,不过由于在大局反常处理器中界说了该反常的处理逻辑,所以程序依照界说的格式回来给了前端,而不是直接将反常抛给前端:

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

6.2 处理HttpMessageNotReadableException反常

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

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

因而需要在大局反常处理器中增加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调用接口进行验证,如下图所示:

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

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反常,如下图所示:

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

因而需要在大局反常处理器中增加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调用接口进行验证,如下图所示:

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

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注解:

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

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

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

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

因而需要在大局反常处理器中增加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调用接口进行验证,如下图所示:

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

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(), "操作失利,请稍后重试");
    }
}