API開發中經常會遇到一些對請求數據進行驗證的情況,這時候如果使用註解就有兩個好處,一是驗證邏輯和業務邏輯分離,代碼清晰,二是驗證邏輯可以輕鬆複用,只需要在要驗證的地方加上註解就可以。
Java提供了一些基本的驗證註解,比如@NotNull
、@Size
,但是更多情況下需要自定義驗證邏輯,這時候就可以自己實現一個驗證註解,方法很簡單,僅需要兩個東西:
- 一個自定義的註解,並且指定驗證器
- 一個驗證器的實現
自定義驗證註解
考慮有一個API,接收一個Student
對象,並希望對象裏的age
域的值是奇數,這時候就可以創建以下註解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = AgeValidator.class)
public @interface Odd {
String message() default "Age Must Be Odd";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
其中:
@Target
指明這個註解要作用在什麼地方,可以是對象、域、構造器等,因爲要作用在age
域上,因此這裏選擇FIELD
@Retention
指明瞭註解的生命週期,可以有SOURCE
(僅保存在源碼中,會被編譯器丟棄),CLASS
(在class文件中可用,會被VM丟棄)以及RUNTIME
(在運行期也被保留),這裏選擇了生命週期最長的RUNTIME
@Constraint
是最關鍵的,它表示這個註解是一個驗證註解,並且指定了一個實現驗證邏輯的驗證器message()
指明瞭驗證失敗後返回的消息,此方法爲@Constraint
要求groups()
和payload()
也爲@Constraint
要求,可默認爲空,詳細用途可以查看@Constraint
文檔
創建驗證器
有了註解之後,就需要一個驗證器來實現驗證邏輯:
public class AgeValidator implements ConstraintValidator<Odd,Integer> {
@Override
public void initialize(Odd constraintAnnotation) {
}
@Override
public boolean isValid(Integer age, ConstraintValidatorContext constraintValidatorContext) {
return age % 2 != 0;
}
}
其中:
- 驗證器有兩個類型參數,第一個是所屬的註解,第二個是註解作用地方的類型,這裏因爲作用在
age
上,因此這裏用了Integer
initialize()
可以在驗證開始前調用註解裏的方法,從而獲取到一些註解裏的參數,這裏用不到isValid()
就是判斷是否合法的地方
應用註解
註解和驗證器創建好之後,就可以使用註解了:
public class Student {
@Odd
private int age;
private String name;
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;
}
}
@RestController
public class StudentResource {
@PostMapping("/student")
public String addStudent(@Valid @RequestBody Student student) {
return "Student Created";
}
}
在需要啓用驗證的地方加上@Valid
註解,這時候如果請求裏的Student
年齡不是奇數,就會得到一個400
響應:
{
"timestamp": "2018-08-15T17:01:44.598+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Odd.student.age",
"Odd.age",
"Odd.int",
"Odd"
],
"arguments": [
{
"codes": [
"student.age",
"age"
],
"arguments": null,
"defaultMessage": "age",
"code": "age"
}
],
"defaultMessage": "Age Must Be Odd",
"objectName": "student",
"field": "age",
"rejectedValue": 12,
"bindingFailure": false,
"code": "Odd"
}
],
"message": "Validation failed for object='student'. Error count: 1",
"path": "/student"
}
也可以手動來處理錯誤,加上一個BindingResult
來接收驗證結果即可:
@RestController
public class StudentResource {
@PostMapping("/student")
public String addStudent(@Valid @RequestBody Student student, BindingResult validateResult) {
if (validateResult.hasErrors()) {
return validateResult.getAllErrors().get(0).getDefaultMessage();
}
return "Student Created";
}
}
這時候如果驗證出錯,便只會返回一個狀態爲200
,內容爲Age Must Be Odd的響應。