Spring Validation引數校驗
簡介
Spring Validation
是在Spring Context
下的,在Spring Boot
專案中,我們引入spring-boot-starter-web
便會引入進來,Spring Validation
是對Hibernate Validator
的二次封裝,使我們可以更方便的在Spring MVC
中完成自動校驗。
Hibernate Validator
是對JSR-303(Bean Validation)
的參考實現。Hibernate Validator
提供了JSR-303
規範中所有內建constraint
的實現,除此之外還有一些附加的constraint
。
JSR-303
constraint
:
Constraint | Description |
---|---|
@Null | 被註解的元素必須為null |
@NotNull | 被註解的元素必須不為null |
@AssertTure | 被註解的元素必須為ture |
@AssertFalse | 被註解的元素必須為false |
@Min(value) | 被註解的元素必須是數字且必須大於等於指定值 |
@Max(value) | 被註解的元素必須是數字且必須小於等於指定值 |
@DecimalMin(value) | 被註解的元素必須是數字且必須大於等於指定值 |
@DecimalMax(value) | 被註解的元素必須是數字且必須小於等於指定值 |
@Size(max, min) | 被註解的元素必須在指定的範圍內 |
@Digits(integer, fraction) | 被註解的元素必須是數字且其值必須在給定的範圍內 |
@Past | 被註解的元素必須是一個過去的日期 |
@Future | 被註解的元素必須是一個將來的日期 |
@Pattern(value) | 被註解的元素必須符合給定正則表示式 |
Hibernate Validator
附加實現的constraint
Constraint | Description |
---|---|
被註解的元素必須是Email 地址 |
|
@Length(min, max) | 被註解的元素長度必須在指定的範圍內 |
@NotEmpty | 被註解的元素必須 |
@Range | 被註解的元素(可以是數字或者表示數字的字串)必須在給定的範圍內 |
@URL | 被註解的元素必須是URL |
當然,我們也可以自定義實現,自定義實現在下面使用中在講吧。
使用
在開始使用之前,先做好準備工作,建立一個Spring Boot
專案,然後引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
只需要引入這個依賴就可以了。
使用@Validated註解攔截校驗
在Controller
中,我們需要校驗前端傳遞過來的引數,我們可以這麼寫
@RestController
public class TestController {
@PostMapping("/test")
public Object test(@RequestBody @Validated User user, BindingResult result) {
if (result.hasErrors()) {
return result.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
}
return user;
}
}
只需要在需要校驗的實體前面打上@Validated
註解就可以了,這時候,如果我們傳遞的引數符合要求,則會正常返回。否則返回:
[
"age欄位不合法",
"name欄位不合法"
]
它會將我們所有不合法資訊一次性全部返回,在日常開發中,我們可以吧校驗BindingResult
是否有錯誤資訊的校驗統一抽出到一個工具類中去做處理,使用專案中統一格式返回錯誤資訊就好。這就是一個最簡單的校驗示例了,其他註解也都是類似的,就不多舉例了,可以自己嘗試著玩玩。
在日常開發中想必都曾遇到過這樣的需求,比如這個age這個欄位,我想要這個欄位只在PC
端校驗,在App
端不做限制,這就需要用到分組校驗了,每個註解都提供了一個group
屬性,利用這個屬性就可以輕易做到以上需求。比如在User上的註解中加入group
屬性,指定其被校驗的group
:
public class User {
@Length(min = 1, max = 22, message = "name欄位不合法", groups = {App.class, PC.class})
private String name;
@Min(value = 1, message = "age欄位不合法", groups = PC.class)
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在Controller中的@Validated中指定當前group
:
@RestController
public class TestController {
@PostMapping("/test")
public Object test(@RequestBody @Validated(App.class) User user, BindingResult result) {
if (result.hasErrors()) {
return result.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
}
return user;
}
}
這時候我再使用兩個不合法欄位訪問返回:
[
"name欄位不合法"
]
可以看到,它並沒有對age
欄位進行校驗。這就是它的分組校驗。
在方法實現中攔截校驗
它不只是在Controller校驗前端傳遞過來的引數的時候可以用,它在方法中同樣可以用,我們可以這樣來使用:
@RestController
public class TestController {
@Autowired
ObjectMapper objectMapper;
@Autowired
SmartValidator smartValidator;
@GetMapping("/test")
public Object test() {
String context = "{\"name\": \"felixu\",\"age\": 0}";
User user = null;
try {
user = objectMapper.readValue(context, User.class);
} catch (IOException e) {
e.printStackTrace();
}
BeanPropertyBindingResult result = new BeanPropertyBindingResult(user, "user");
smartValidator.validate(user, result);
if (result.hasErrors()) {
return result.getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
}
return user;
}
}
使用需要被校驗的實體構造BeanPropertyBindingResult
物件,然後將傳遞給SmartValidator
的validate
方法來完成跟上面相同的校驗。validate
有個過載方法,也接收分組,所以這種方式同樣可以實現分組校驗。
自定義實現
需求總是多變的,有時候,可能上面的校驗方式並不能滿足我們的要求,這時候就需要我們自定義一下校驗了,要做到自定義註解來校驗,我們需要做以下兩步,首先實現ConstraintValidator<A extends Annotation, T>
(ps:原諒我的自戀。。。):
public class IsFelixuValidator implements ConstraintValidator<IsFelixu, String> {
@Override
public void initialize(IsFelixu constraintAnnotation) {
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if ("felixu".equals(value)) {
return true;
}
return false;
}
}
isValid
便是我們的校驗邏輯,true
為通過校驗。
然後我們實現註解:
@Documented
@Constraint(
// 指定對應的校驗類
validatedBy = {IsFelixuValidator.class}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IsFelixu {
String message() default "this value is not felixu";
// 這兩個屬性必須要存在
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
這樣就ok
了,我們繼續使用之前的來做測試,在User
的name
屬性上加上@IsFelixu
註解,此時測試,如果不傳遞name
為felixu
的值,則會提示如下資訊:
[
"this value is not felixu",
"age欄位不合法"
]
總結
JSR-303
的釋出使得在資料自動繫結和驗證變得簡單,使開發人員在定義資料模型時不必考慮實現框架的限制。當然Bean Validation
還只是提供了一些最基本的constraint
。
上面只是相對簡單的用法,也是我們現在專案中所用到的方式,在實際的開發過程中,使用者可以根據自己的需要組合或開發出更加複雜的constraint
。這就需要想象力了,從上面的用法中應該可以想到很多地方可以去使用,但是設計和實現時,往往需要考慮諸多因素,比如易用性和封裝的複雜度,等等方面,還需要自己去考量了。