在日常的开发作业中,为了保证落库数据的完整性,参数校验绝对是必不可少的一部分,本篇文章就来解说下在项目中该如何高雅的校验参数。
假定有一个新增学员的接口,一般第一步咱们都会先校验学员信息是否正确,然后才会落库,简单起见,假定新增学员时只有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(), "操作失利,请稍后重试");
}
}