在項目開發中許多地方需要加以驗證,對於使用if-else簡單粗暴一個一個驗證,spring的validation封裝了Javax ValidationI校驗參數,大大縮減了代碼量。
以前的分層驗證,從controller到落入數據庫,一層一層驗證,代碼重復、冗余。
Javax ValidationI使用Java Bean驗證通過注解將約束添加到域模型中,將驗證邏輯從代碼中分離出來。
Javax ValidationI的依賴:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.16.Final</version>
</dependency>
springboot對Javax ValidationI封裝,依賴變成
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
一、初級注解
JSR提供的驗證注解:
@Null 被注釋的元素必須為 null
@NotNull 被注釋的元素必須不為 null
@AssertTrue 被注釋的元素必須為 true
@AssertFalse 被注釋的元素必須為 false
@Min(value) 被注釋的元素必須是一個數字,其值必須大於等於指定的最小值  
@Max(value) 被注釋的元素必須是一個數字,其值必須小於等於指定的最大值
@DecimalMin(value) 被注釋的元素必須是一個數字,其值必須大於等於指定的最小值
@DecimalMax(value) 被注釋的元素必須是一個數字,其值必須小於等於指定的最大值
@Size(max=, min=) 被注釋的元素的大小必須在指定的范圍內
@Digits (integer, fraction) 被注釋的元素必須是一個數字,其值必須在可接受的范圍內
@Past 被注釋的元素必須是一個過去的日期
@Future 被注釋的元素必須是一個將來的日期
@Pattern(regex=,flag=) 被注釋的元素必須符合指定的正則表達式
Validator提供的驗證注解:
@NotBlank(message =) 驗證字符串非null,且長度必須大於0
@Email 被注釋的元素必須是電子郵箱地址
@Length(min=,max=) 被注釋的字符串的大小必須在指定的范圍內
@NotEmpty 被注釋的字符串的必須非空
@Range(min=,max=,message=) 被注釋的元素必須在合適的范圍內
二、創建驗證器
/**
* 驗證測試類
*/
public class ValidationTest {
// 驗證器對象
private Validator validator;
// 待驗證對象
private UserInfo userInfo;
// 驗證結果集合
private Set<ConstraintViolation<UserInfo>> set;
/**
* 初始化操作
*/
@Before
public void init() {
// 初始化驗證器
validator = Validation.buildDefaultValidatorFactory()
.getValidator();
// 初始化待驗證對象 - 用戶信息
userInfo = new UserInfo();
}
/**
* 結果打印
*/
@After
public void print() {
set.forEach(item -> {
// 輸出驗證錯誤信息
System.out.println(item.getMessage());
});
}
@Test
public void nullValidation() {
// 使用驗證器對對象進行驗證
set = validator.validate(userInfo);
}
}
三、中級約束
中級約束中分為分組約束、組序列
public class UserInfo {
// 登錄場景
public interface LoginGroup {}
// 注冊場景
public interface RegisterGroup {}
/**
* 用戶ID
*/
@NotNull(message = "用戶ID不能為空",
groups = LoginGroup.class)
private String userId;
/**
* 用戶名
* NotEmpty 不會自動去掉前后空格
*/
@NotEmpty(message = "用戶名稱不能為空",groups = RegisterGroup.class)
private String userName;
/**
* 用戶密碼
* NotBlank 自動去掉字符串前后空格后驗證是否為空
*/
@NotBlank(message = "用戶密碼不能為空")
@Length(min = 6, max = 20,
message = "密碼長度不能少於6位,多於20位")
private String passWord;
}
/**
* 分組驗證測試方法
*/
@Test
public void groupValidation() {
set = validator.validate(userInfo,
UserInfo.RegisterGroup.class,
UserInfo.LoginGroup.class);
}
組排序:
public class UserInfo {
// 登錄場景
public interface LoginGroup {}
// 注冊場景
public interface RegisterGroup {}
// 組排序場景
@GroupSequence({
LoginGroup.class,
RegisterGroup.class,
Default.class
})
public interface Group {}
/**
* 用戶ID
*/
@NotNull(message = "用戶ID不能為空",
groups = LoginGroup.class)
private String userId;
/**
* 用戶名
* NotEmpty 不會自動去掉前后空格
*/
@NotEmpty(message = "用戶名稱不能為空",
groups = RegisterGroup .class)
private String userName;
/**
* 用戶密碼
* NotBlank 自動去掉字符串前后空格后驗證是否為空
*/
@NotBlank(message = "用戶密碼不能為空")
@Length(min = 6, max = 20,
message = "密碼長度不能少於6位,多於20位")
private String passWord;
}
/**
* 組序列測試
*/
@Test
public void groupSequenceValidation() {
set = validator.validate(userInfo,
UserInfo.Group.class);
}
三、高級約束
高級約束是對參數、返回值的約束。
使用注解:
javax:@Valid
spring:@Validated
在檢驗參數、返回值是否符合規范時,使用@Validated或者@Valid在基本驗證功能上沒有太多區別。但是在分組、注解地方、嵌套驗證等功能上兩個有所不同。
@Validated:提供了一個分組功能,可以在入參、返回值驗證時,根據不同的分組采用不同的驗證機制。
@Valid:不支持分組功能。
注解地方:
@Validated:可以用在類型、方法和方法參數上。但是不能用在成員屬性(字段)上。
@Valid:可以用在方法、構造函數、方法參數和成員屬性(字段)上。
/**
* 用戶信息
*/
public class UserInfoService {
/**
* UserInfo 作為輸入參數
* @param userInfo
*/
public void setUserInfo(@Valid UserInfo userInfo) {}
/**
* UserInfo 作為輸出參數
* @return
*/
public @Valid UserInfo getUserInfo() {
return new UserInfo();
}
}
/**
* 驗證測試類
*/
public class ValidationTest {
// 驗證器對象
private Validator validator;
// 待驗證對象
private UserInfo userInfo;
// 驗證結果集合
private Set<ConstraintViolation<UserInfoService>> otherSet;
/**
* 初始化操作
*/
@Before
public void init() {
// 初始化驗證器
validator = Validation.buildDefaultValidatorFactory()
.getValidator();
// 初始化待驗證對象 - 用戶信息
userInfo = new UserInfo();
}
/**
* 結果打印
*/
@After
public void print() {
set.forEach(item -> {
// 輸出驗證錯誤信息
System.out.println(item.getMessage());
});
}
/**
* 對方法輸入參數進行約束注解校驗
*/
@Test
public void paramValidation() throws NoSuchMethodException {
// 獲取校驗執行器
ExecutableValidator executableValidator =
validator.forExecutables();
// 待驗證對象
UserInfoService service = new UserInfoService();
// 待驗證方法
Method method = service.getClass()
.getMethod("setUserInfo", UserInfo.class);
// 方法輸入參數
Object[] paramObjects = new Object[]{new UserInfo()};
// 對方法的輸入參數進行校驗
otherSet = executableValidator.validateParameters(
service,
method,
paramObjects);
}
/**
* 對方法返回值進行約束校驗
*/
@Test
public void returnValueValidation()
throws NoSuchMethodException,
InvocationTargetException, IllegalAccessException {
// 獲取校驗執行器
ExecutableValidator executableValidator =
validator.forExecutables();
// 構造要驗證的方法對象
UserInfoService service = new UserInfoService();
Method method = service.getClass()
.getMethod("getUserInfo");
// 調用方法得到返回值
Object returnValue = method.invoke(service);
// 校驗方法返回值是否符合約束
otherSet = executableValidator.validateReturnValue(
service,
method,
returnValue);
}
}
在controller中驗證入參一般使用@Validated
@RequestMapping(method = RequestMethod.POST)
public UserInfo create(@RequestBody @Validated( { RegisterGroup.class }) UserInfo userInfo) {
return userService.create(userInfo);
}
@RequestMapping(method = RequestMethod.GET)
public UserInfo getUserById(@NotNull(message = "id不能為空") int userId) {
return userService.getUserById(userId);
}
四、自定義約束注解
/**
* 自定義手機號約束注解
*/
@Documented
// 注解的作用目標
@Target({ElementType.FIELD})
// 注解的保留策略
@Retention(RetentionPolicy.RUNTIME)
// 不同之處:於約束注解關聯的驗證器
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
// 約束注解驗證時的輸出信息
String message() default "手機號校驗錯誤";
// 約束注解在驗證時所屬的組別
Class<?>[] groups() default {};
// 約束注解的有效負載(嚴重程度)
Class<? extends Payload>[] payload() default {};
}
/**
* 自定義手機號約束注解關聯驗證器
*/
public class PhoneValidator
implements ConstraintValidator<Phone, String> {
/**
* 自定義校驗邏輯方法
* @param value
* @param context
* @return
*/
@Override
public boolean isValid(String value,
ConstraintValidatorContext context) {
// 手機號驗證規則:158后頭隨便
String check = "158\\d{8}";
Pattern regex = Pattern.compile(check);
// 空值處理
String phone = Optional.ofNullable(value).orElse("");
Matcher matcher = regex.matcher(phone);
return matcher.matches();
}
}
自定義注解使用:
public class UserInfo{
/**
* 手機號
*/
@Phone(message = "手機號不是158后頭隨便")
private String phone;
}
