在開發中經常需要寫一些字段校驗的代碼,比如字段非空,字段長度限制,郵箱格式驗證等等
hibernate validator
(官方文檔)提供了一套比較完善、便捷的驗證實現方式。
spring-boot-starter-web
包里面有hibernate-validator
包,不需要引用hibernate validator
依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
如果是Spring Mvc
,那可以直接添加hibernate-validator
依賴
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
</dependency>
1. 常見的注解
注解 | 說明 |
---|---|
@Null |
被注釋的元素必須為 null |
@NotNull |
被注釋的元素必須不為 null |
@AssertTrue |
被注釋的元素必須為 true |
@AssertFalse |
被注釋的元素必須為 false |
@Min(value=) |
被注釋的元素必須是一個數字,其值必須大於等於指定的最小值 |
@Max(value=) |
被注釋的元素必須是一個數字,其值必須小於等於指定的最大值 |
@DecimalMin(value=, inclusive=) |
被注釋的元素必須是一個數字,其值必須大於等於指定的最小值 |
@DecimalMax(value=, inclusive=) |
被注釋的元素必須是一個數字,其值必須小於等於指定的最大值 |
@Future |
檢查被注釋的日期是否是將來的日期 |
@FutureOrPresent |
檢查被注釋的日期是現在還是將來 |
@NotEmpty |
檢查被注釋的元素是否不為null或為空 |
@Negative |
檢查被注釋的元素是否嚴格為負。零值被視為無效。 |
@NegativeOrZero |
檢查被注釋的元素是負數還是零。 |
@Past |
檢查被注釋的日期是否是過去的日期 |
@PastOrPresent |
檢查被注釋的日期是過去還是現在 |
@Pattern(regex=, flags=) |
檢查被注釋的字符串是否與正則表達式匹配match |
@Positive |
檢查被注釋元素是否嚴格為正。零值被視為無效。 |
@PositiveOrZero |
檢查被注釋元素是正數還是零。 |
@Size(min=, max=) |
檢查被注釋的元素的大小是否介於min 和之間max (包括) |
@Email |
檢查被注釋的元素是否為有效的電子郵件地址。 |
@Digits(integer=, fraction=) |
被注釋的元素必須是一個數字,其值必須在可接受的范圍內 |
@Currency(value=) |
檢查帶注釋的貨幣單位javax.money.MonetaryAmount 是否為指定貨幣單位的一部分。 |
@ISBN |
檢查帶注釋的字符序列是有效的ISBN。type 確定ISBN的類型。默認值為ISBN-13。 |
@Length(min=, max=) |
被注釋的字符串的大小必須在指定的范圍內 |
@Range(min=, max=) |
被注釋的元素必須在合適的范圍內 |
@SafeHtml(whitelistType= , additionalTags=, additionalTagsWithAttributes=, baseURI=) |
檢查帶注釋的值是否包含潛在的惡意片段,例如<script/> 。為了使用此約束,jsoup庫必須是類路徑的一部分。通過該whitelistType 屬性,可以選擇預定義的白名單類型,可以通過additionalTags 或進行細化additionalTagsWithAttributes 。前者允許添加沒有任何屬性的標簽,而后者則允許使用注釋指定標簽和可選的允許屬性以及該屬性的可接受協議@SafeHtml.Tag 。另外,baseURI 允許指定用於解析相對URI的基本URI。 |
@UniqueElements |
檢查被注釋的集合僅包含唯一元素 |
@URL(protocol=, host=, port=, regexp=, flags=) |
根據RFC2396檢查帶注釋的字符序列是否為有效URL。如果任何可選參數protocol ,host 或port 指定時,相應的URL片段必須在指定的值相匹配。可選參數,regexp 並flags 允許指定URL必須匹配的其他正則表達式(包括正則表達式標志)。默認情況下,此約束使用java.net.URL 構造函數來驗證給定的字符串是否表示有效的URL。也提供基於正則表達式的版本RegexpURLValidator -可以通過XML(請參見第8.2節“通過映射約束constraint-mappings ”)或編程API(請參見第12.14.2節“以編程方式添加約束定義”)進行配置。 |
2. 動手實踐(快速上手)
使用idea新建一個springboot
項目並引入web
包
新建一個User的bean
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.*;
/**
* @author john
* @date 2020/4/9 - 10:46
*/
@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String id;
@NotBlank(message = "用戶名不能為空")
@Length(min = 4, max = 8, message = "用戶名長度不在4-8之間")
private String name;
@NotNull(message = "密碼不能為空")
@Pattern(regexp = "[0-9]\\d+", message = "密碼不符合規范")
private String password;
@NotNull(message = "年齡不能為空")
@Range(min = 1, max = 140, message = "年齡值不太正常")
private Integer age;
@Max(value = 10, message = "級別超過最大值了")
@Min(value = 1, message = "級別低於最小值")
private Integer level;
private String mobile;
}
在創建一個響應數據的bean
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
/**
* @author john
* @date 2020/4/9 - 11:16
*/
@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
private boolean status;
private T data;
private List<String> messages;
}
在創建一個控制器
package com.example.validatordemo.controller;
import com.example.validatordemo.bean.User;
import com.example.validatordemo.response.ApiResponse;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* @author john
* @date 2020/4/9 - 11:04
*/
@RestController
@RequestMapping("/user")
public class UserController {
//創建用戶
@PostMapping
public ApiResponse<User> register(@Valid @RequestBody User user, BindingResult errors) {
//判斷傳入用戶數據是否合法
List<String> errs = new ArrayList<>();
if (errors.hasErrors()) {
errors.getAllErrors().stream().forEach(error -> {
FieldError fieldError = (FieldError) error;
errs.add(fieldError.getDefaultMessage());
});
return new ApiResponse<User>(false, null, errs);
}
//輸入入庫
System.out.println("數據插入成功");
return new ApiResponse<User>(false, user, errs);
}
//修改用戶
@PutMapping
public ApiResponse<User> update(@Valid @RequestBody User user, BindingResult errors) {
//判斷傳入用戶數據是否合法
List<String> errs = new ArrayList<>();
if (errors.hasErrors()) {
errors.getAllErrors().stream().forEach(error -> {
FieldError fieldError = (FieldError) error;
errs.add(fieldError.getDefaultMessage());
});
return new ApiResponse<>(false, null, errs);
}
//輸入入庫
System.out.println("數據修改成功");
return new ApiResponse<>(false, user, errs);
}
}
上面分別定義了創建用戶和修改用戶的操作
測試創建用戶結果
由此可見當傳入的數據不符合規范時會被識別並處理
3 . hibernate的校驗模式
上面例子中一次性返回了所有驗證不通過的集合,通常按順序驗證到第一個字段不符合驗證要求時,就可以直接拒絕請求了.
Hibernate Validator
有以下兩種驗證模式:
1 、普通模式(默認是這個模式)
普通模式(會校驗完所有的屬性,然后返回所有的驗證失敗信息)
2、快速失敗返回模式
快速失敗返回模式(只要有一個驗證失敗,則返回)
> 默認是普通模式
配置校驗模式
基於上面的案例,繼續操作,配置hibernate Validator
為快速失敗返回模式:
增加如下代碼
package com.example.validatordemo.conf;
import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
/**
* @author john
* @date 2020/4/9 - 11:47
*/
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.addProperty("hibernate.validator.fail_fast", "true")
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
再次重啟項目,使用postman進行測試
可見此時在遇到第一個不匹配的數據后就結束校驗,並快速返回
多個參數的,可以加多個@Valid和BindingResult,如:
public void test()(@RequestBody @Valid DemoModel demo, BindingResult result)
public void test()(@RequestBody @Valid DemoModel demo, BindingResult result,@RequestBody @Valid DemoModel demo2, BindingResult result2)
4. 分組的使用
在上面的例子中,我們定義了user的兩個方法一個添加用戶,一個是修改用戶,正常來說當添加用戶操作時,此時的用戶id屬性應該為空,而當用戶處理修改狀態時,用戶的id應該已經存在,因此我們對id在兩種不同的情況下應該區別對待,如何校驗用戶的id呢,這時我們可以使用分組實現.
具體操作如下
先新建組
package com.example.validatordemo.validator;
import javax.validation.groups.Default;
/**
* @author john
* @date 2020/4/9 - 12:00
*/
public interface AddUserGroup extends Default {
}
package com.example.validatordemo.validator;
import javax.validation.groups.Default;
/**
* @author john
* @date 2020/4/9 - 12:00
*/
public interface UpdateUserGroup extends Default {
}
修改User的bean,修改部分如下
public class User {
...
@NotNull(message = "id不能為空", groups = {UpdateUserGroup.class})
private String id;
...
}
修改控制器代碼
public class UserController {
...
//修改用戶
@PutMapping
public ApiResponse<User> update(@Validated(UpdateUserGroup.class) @RequestBody User user, BindingResult errors) {
...
}
}
執行測試,當執行插入用戶時
當執行修改操作時
可見此時的不同操作時,對id有不同的判斷
5. 自定義校驗的使用
在剛才的操作中,我們對一個字段一直沒有校驗,那就是mobile,使用內置的校驗應該無法滿足需求,這時候我們可以自定義校驗來實現對mobile字段的校驗
定義自定義約束,有三個步驟
- 創建約束注解
- 實現一個驗證器
- 定義默認的錯誤信息
創建約束注解
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author john
* @date 2020/4/9 - 12:39
*/
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = {MobileValidator.class})
@Retention(RUNTIME)
@Repeatable(Mobile.List.class)
public @interface Mobile {
/**
* 錯誤提示信息,可以寫死,也可以填寫國際化的key
*/
String message() default "手機號碼不正確";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String regexp() default "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@interface List {
Mobile[] value();
}
}
實現一個驗證器
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.regex.Pattern;
/**
* @author john
* @date 2020/4/9 - 12:41
*/
public class MobileValidator implements ConstraintValidator<Mobile, String> {
/**
* 手機驗證規則
*/
private Pattern pattern;
@Override
public void initialize(Mobile mobile) {
pattern = Pattern.compile(mobile.regexp());
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return pattern.matcher(value).matches();
}
}
定義默認的錯誤信息
public class User {
....
@Mobile(message = "手機號碼格式異常")
private String mobile;
}
重啟項目執行測試
此時手機格式校驗失敗
6. 代碼
7. 參考
springboot使用hibernate validator校驗
如何優雅的做數據校驗-Hibernate Validator詳細使用說明
Hibernate Validator 6.1.2.Final - Jakarta Bean Validation Reference Implementation: Reference Guide