淺析Spring自定義註解+aop實現對實體類的欄位進行校驗
阿新 • • 發佈:2022-03-18
API開發中經常會遇到一些對請求資料進行驗證的情況,這時候如果使用註解就有兩個好處:
1、一是驗證邏輯和業務邏輯分離,程式碼清晰
2、二是驗證邏輯可以輕鬆複用,只需要在要驗證的地方加上註解就可以
因此,我們在業務開發過程中經常遇到形形色色的註解(Java提供了一些基本的驗證註解,比如 @NotNull
、@Size
),框架自有的註解並不是總能滿足複雜的業務需求,我們可以自定義註解來滿足我們的需求。方法很簡單,僅需要兩個東西:
- 一個自定義的註解,並且指定驗證器
- 一個驗證器的實現
欄位註解一般是用於校驗欄位是否滿足要求,比如JSR303校驗依賴就提供了很多校驗註解 ,如@NotNull、@Range等,但是這些註解並不是能夠滿足所有業務場景的。比如我們希望傳入的引數在指定的String集合中,那麼已有的註解就不能滿足需求了,需要自己實現。
1、定義一個@Check註解,通過@interface宣告一個註解
// 校驗物件裡的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 {};
}
其中:@Constraint 通過使用validatedBy來指定與註解關聯的驗證器
@Target
指明這個註解要作用在什麼地方,可以是物件、域、構造器等,因為要作用在age
域上,因此這裡選擇FIELD
@Retention
指明瞭註解的生命週期,可以有SOURCE
(僅儲存在原始碼中,會被編譯器丟棄),CLASS
(在class檔案中可用,會被VM丟棄)以及RUNTIME
(在執行期也被保留),這裡選擇了生命週期最長的RUNTIME
@Constraint
是最關鍵的,它表示這個註解是一個驗證註解,並且指定了一個實現驗證邏輯的驗證器message()
指明瞭驗證失敗後返回的訊息,此方法為@Constraint
groups()
和payload()
也為@Constraint
要求,可預設為空,詳細用途可以檢視@Constraint
文件
2、建立驗證器:驗證器類需要實現ConstraintValidator泛型介面
有了註解之後,就需要一個驗證器來實現驗證邏輯:這個就是上面 @Constraint 通過使用validatedBy 指定的類
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;
}
}
- 驗證器有兩個型別引數,第一個是所屬的註解(名稱Odd),第二個是註解作用地方的型別,這裡因為作用在
age
上,因此這裡用了Integer
initialize()
可以在驗證開始前呼叫註解裡的方法,從而獲取到一些註解裡的引數isValid()
就是判斷是否合法的地方
3、使用方式:定義一個實體類
@Data
public class User { // 性別只能從 man or women 裡取 @Check(paramValues = {"man", "women"} private String sex;
// 年齡只能為奇數
@Odd(message = "年齡不對")
private Integer age; }
也可以自定義message,就像上面 age 那樣。
另外需要注意的是:在需要啟用驗證的地方一定要加上@Valid
註解,這時候如果請求裡的Student
年齡不是奇數,就會得到一個400
響應:
@RestController
public class StudentResource {
@PostMapping("/student")
public String addStudent(@Valid @RequestBody User user) {
return "Student Created";
}
}
{
"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";
}
}
這樣我們就可以使用自定義引數校驗來補充JSR303預設校驗不夠的情況咯,挺好用的。