1. 前言
簡述JSR303/JSR-349,hibernate validation,spring validation之間的關係。JSR303是一項標準,JSR-349是其的升級版本,添加了一些新特性,他們規定一些校驗規範即校驗註解,如@Null,@NotNull,@Pattern,他們位於javax.validation.constraints包下,只提供規範不提供實現。而hibernate validation是對這個規範的實踐(不要將hibernate和數據庫orm框架聯繫在一起),他提供了相應的實現,並增加了一些其他校驗註解,如@Email,@Length,@Range等等,他們位於org.hibernate.validator.constraints包下。而萬能的spring爲了給開發者提供便捷,對hibernate validation進行了二次封裝,顯示校驗validated bean時,你可以使用spring validation或者hibernate validation,而spring validation另一個特性,便是其在springmvc模塊中添加了自動校驗,並將校驗信息封裝進了特定的類中。這無疑便捷了我們的web開發。本文主要介紹在springmvc中自動校驗的機制。
2. 常用的校驗方式
限制 | 說明 |
---|---|
@Null | 限制只能爲null |
@NotNull | 限制必須不爲null |
@AssertFalse | 限制必須爲false |
@AssertTrue | 限制必須爲true |
@DecimalMax(value) | 限制必須爲一個不大於指定值的數字 |
@DecimalMin(value) | 限制必須爲一個不小於指定值的數字 |
@Digits(integer,fraction) | 限制必須爲一個小數,且整數部分的位數不能超過integer,小數部分的位數不能超過fraction |
@Future | 限制必須是一個將來的日期 |
@Max(value) | 限制必須爲一個不大於指定值的數字 |
@Min(value) | 限制必須爲一個不小於指定值的數字 |
@Past | 限制必須是一個過去的日期 |
@Pattern(value) | 限制必須符合指定的正則表達式 |
@Size(max,min) | 限制字符長度必須在min到max之間 |
@Past | 驗證註解的元素值(日期類型)比當前時間早 |
@NotEmpty | 驗證註解的元素值不爲null且不爲空(字符串長度不爲0、集合大小不爲0) |
@NotBlank | 驗證註解的元素值不爲空(不爲null、去除首位空格後長度爲0),不同於@NotEmpty,@NotBlank只應用於字符串且在比較時會去除字符串的空格 |
驗證註解的元素值是Email,也可以通過正則表達式和flag指定自定義的email格式 |
更多方式請查看源代碼路徑下提供的全量校驗方式。
3. 自定義註解添加校驗方法
例如:我們校驗手機號或身份證號,官方提供的註解中沒有支持的,當然我們可以通過官方提供的正則表達式來校驗:
@Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手機號碼格式錯誤")
@NotBlank(message = "手機號碼不能爲空")
private String phone;
但是這種方式並不是很方便,我們可以自定義一個校驗規則的註解
3.1 定義手機號校驗註解 @Phone
@Target({ ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
/**
* 校驗不通過的message
*/
String message() default "請輸入正確的手機號";
/**
* 分組校驗
*/
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
3.2 定義校驗方式
public class PhoneValidator implements ConstraintValidator<Phone, String> {
@Override
public void initialize(Phone constraintAnnotation) {
}
@Override
public boolean isValid(String phone, ConstraintValidatorContext constraintValidatorContext) {
if(!StringUtils.isEmpty(phone)){
//獲取默認提示信息
String defaultConstraintMessageTemplate = constraintValidatorContext.getDefaultConstraintMessageTemplate();
System.out.println("default message :" + defaultConstraintMessageTemplate);
//禁用默認提示信息
constraintValidatorContext.disableDefaultConstraintViolation();
//設置提示語
constraintValidatorContext.buildConstraintViolationWithTemplate("手機號格式錯誤").addConstraintViolation();
String regex = "^1(3|4|5|7|8)\\d{9}$";
return phone.matches(regex);
}
return true;
}
}
4. 引入依賴
我們使用maven構建springboot應用來進行demo演示。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
我們只需要引入spring-boot-starter-web依賴即可,如果查看其子依賴,可以發現如下的依賴:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
驗證了我之前的描述,web模塊使用了hibernate-validation,並且databind模塊也提供了相應的數據綁定功能。
5. 構建簡單Demo項目
5.1 構建啓動類
無需添加其他註解,一個典型的啓動類
@SpringBootApplication
public class ValidateApp {
public static void main(String[] args) {
SpringApplication.run(ValidateApp.class, args);
}
}
5.2 創建需要被校驗的實體類
@Data
public class UserEntity {
@NotBlank
private String name;
@Range(max = 150, min = 1, message = "年齡範圍應該在1-150內。")
private Integer age;
@Email(message = "郵箱格式錯誤")
private String email;
@NotEmpty(message = "密碼不能爲空")
@Length(min = 6, max = 8, message = "密碼長度爲6-8位。")
private String password;
@Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手機號碼格式錯誤")
@NotBlank(message = "手機號碼不能爲空")
private String phone;
@IdCard
private String idCard;
}
5.3 在Controller中開啓校驗
在Controller 中 請求參數上添加@Validated 標籤開啓驗證
@RestController
@Slf4j
public class TestController {
@PostMapping("/user")
public String test1(@RequestBody @Validated UserEntity userEntity){
log.info("user is {}",userEntity);
return "success";
}
}
5.4 校驗結果
{
"timestamp": "2019-03-10T09:29:20.978+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotBlank.userEntity.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"userEntity.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "不能爲空",
"objectName": "userEntity",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='userEntity'. Error count: 1",
"path": "/user"
}
這個結果我們可以進行統一處理,篩選出適合給前端返回的錯誤提示文案。關於統一處理異常,在這篇文章中已經提到:https://segmentfault.com/a/1190000018278325
5.5 異常處理
@ControllerAdvice
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {
/**
* 分隔符
*/
private static final String SEPARATOR = ",";
/**
* 攔截數據校驗異常
*
* @param request 請求
* @param e 校驗異常
* @return 通用返回格式
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ZingResult notValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
log.error("請求的url爲{}出現數據校驗異常,異常信息爲:", request.getRequestURI(), e);
BindingResult bindingResult = e.getBindingResult();
List<String> errorMsgList = new ArrayList();
for (FieldError fieldError : bindingResult.getFieldErrors()) {
errorMsgList.add(fieldError.getDefaultMessage());
}
return ZingResult.error(ExceptionEnum.PARAM_ERROR,errorMsgList);
}
}
6. 總結
這個框架校驗還有其他多種用法,如,分組校驗、手動校驗等,我總結的這篇博客也是參照該文章。詳情參看:https://blog.csdn.net/u013815546/article/details/77248003/
最後該作者的總結也非常好:
我推崇的方式,是僅僅使用自帶的註解和自定義註解,完成一些簡單的,可複用的校驗。尋求一個易用性和封裝複雜性之間的平衡點是我們作爲工具使用者應該考慮的。