在日常的開發作業中,為了保證落庫資料的完整性,引數校驗絕對是必不可少的一部分,本篇文章就來講解下在專案中該如何優雅的校驗引數,
假設有一個新增學員的介面,一般第一步我們都會先校驗學員資訊是否正確,然后才會落庫,簡單起見,假設新增學員時只有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了:

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 = "https://www.cnblogs.com/zwwhnly/archive/2022/12/22/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 = "https://www.cnblogs.com/zwwhnly/archive/2022/12/22/30000", message = "賬戶余額必須小于或等于30000")
private BigDecimal balance;
效果如下圖所示:

注意事項:
1)@DecimalMax注解識別不了欄位值為null的場景:

2)如果將@DecimalMax注解使用在不支持的Java型別,程式會拋出javax.validation.UnexpectedTypeException例外:
@DecimalMax(value = "https://www.cnblogs.com/zwwhnly/archive/2022/12/22/30000", message = "賬戶余額必須小于或等于30000")
private Boolean newStudent;

4.4 @DecimalMin
作用:被標記的元素值必須大于或等于指定的值,
其余的和@DecimalMax注解一致,
使用示例:
@DecimalMin(value = "https://www.cnblogs.com/zwwhnly/archive/2022/12/22/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 = https://www.cnblogs.com/zwwhnly/archive/2022/12/22/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 = https://www.cnblogs.com/zwwhnly/archive/2022/12/22/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 = https://www.cnblogs.com/zwwhnly/archive/2022/12/22/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 = https://www.cnblogs.com/zwwhnly/archive/2022/12/22/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(), "操作失敗,請稍后重試");
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/540503.html
標籤:其他
