前言

後臺開發中對引數的校驗是不可缺少的一個環節,為了解決如何優雅的對引數進行校驗?

  • JSR303(Java Specification Requests)應運而生,JSR303 是JavaBean引數校驗的標準。
  • Bean Validation 為 JavaBean 驗證定義了相應的元資料模型和 API。
  • Hibernate validator 5 是 Bean Validation 1.1的實現。

常見註解

  • Bean Validation中定義的註解:
註解 詳細資訊
@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(value) 被註釋的元素必須符合指定的正則表示式
  • Hibernate validator 在JSR303的基礎上對校驗註解進行了擴充套件,擴充套件註解如下:
註解 詳細資訊
@Email 被註釋的元素必須是電子郵箱地址
@Length 被註釋的字串的大小必須在指定的範圍內
@NotEmpty 被註釋的字串的必須非空
@Range 被註釋的元素必須在合適的範圍內

引數校驗的應用

依賴

  1. <!-- hibernate validator-->
  2. <dependency>
  3. <groupId>org.hibernate</groupId>
  4. <artifactId>hibernate-validator</artifactId>
  5. <version>5.2.4.Final</version>
  6. </dependency>

簡單的引數校驗示例

  • 要想開啟引數校驗,需要在類上標註@Validated註解
  • 控制器
  1. @PostMapping(value = "/test")
  2. public ValidOneEvt param(@RequestBody @Validated ValidOneEvt evt){
  3. return evt;
  4. }
  • 實體驗證
  1. @Getter
  2. @Builder
  3. public class ValidOneEvt {
  4. @NotEmpty(message = "名稱不能為空")
  5. private String name;
  6. private String sex;
  7. }
  • 測試結果

級聯校驗

  • 級聯校驗需要在校驗的實體上新增@Valid。
  • 控制器
  1. @PostMapping(value = "/test")
  2. public ValidOneEvt param(@RequestBody @Validated ValidOneEvt evt){
  3. return evt;
  4. }
  • 實體驗證
  1. @Getter
  2. @Builder
  3. public class ValidOneEvt {
  4. @NotEmpty(message = "名稱不能為空")
  5. private String name;
  6. private String sex;
  7. @Valid
  8. private ValidTwoEvt validTwoEvt;
  9. }
  • 級聯實體
  1. @Getter
  2. @Setter
  3. public class ValidTwoEvt {
  4. @Length(min = 2)
  5. private String name;
  6. }
  • 請求示例

  • 校驗結果

@Validated 與 @Valid

兩者具有相似性

註解地方

  • @Valid:可以用在方法、建構函式、方法引數和成員屬性(欄位)上。
  • @Validated:可以用在型別、方法和方法引數上,不能用在成員屬性(欄位)上。

@Validated和@Valid在級聯驗證功能上的區別

  • @Valid:用在方法入參上無法單獨提供級聯驗證功能。能夠用在成員屬性(欄位)上,提示驗證框架進行級聯驗證。

  • @Validated:用在方法入參上無法單獨提供級聯驗證功能。不能用在成員屬性(欄位)上,也無法提示框架進行級聯驗證。能配合級聯驗證註解@Valid進行級聯驗證。

  • 總結: 通常使用@Validated, 級聯驗證使用@Valid。

自定義校驗註解

自定義校驗註解用於基礎校驗註解不能滿足業務需求。

  • 自定義效驗註解驗證密碼是否相等
  1. import javax.validation.Constraint;
  2. import javax.validation.Payload;
  3. import java.lang.annotation.*;
  4. /**
  5. * @Description 自定義引數校驗註解
  6. * @Documented 註解中的註釋加入文件
  7. * @Retention 註解保留階段 RetentionPolicy.RUNTIME 執行階段
  8. * @Target 作用目標,註解的使用範圍 TYPE:用於描述類、介面(包括註解型別) 或enum宣告
  9. * @Constraint 將註解和註解關聯類關聯到一起
  10. * @author coisini
  11. * @date Aug 10, 2021
  12. * @Version 1.0
  13. */
  14. @Documented
  15. @Retention(RetentionPolicy.RUNTIME)
  16. @Target({ElementType.TYPE})
  17. @Constraint(validatedBy = PasswordValidator.class)
  18. public @interface PasswordEquals {
  19. int min() default 4;
  20. int max() default 6;
  21. String message() default "passwords are not equal";
  22. Class<?>[] groups() default {};
  23. Class<? extends Payload>[] payload() default {};
  24. }
  • 自定義校驗註解關聯類
  1. import javax.validation.ConstraintValidator;
  2. import javax.validation.ConstraintValidatorContext;
  3. /**
  4. * @Description 自定義校驗註解關聯類
  5. * ConstraintValidator的第一個引數:註解的型別
  6. * ConstraintValidator的第二個引數:自定義註解修飾的目標的型別
  7. * @author coisini
  8. * @date Aug 10, 2021
  9. * @Version 1.0
  10. */
  11. public class PasswordValidator implements ConstraintValidator<PasswordEquals, ValidEvt> {
  12. private int min;
  13. private int max;
  14. /**
  15. * 初始化獲取註解引數
  16. * @param constraintAnnotation
  17. */
  18. @Override
  19. public void initialize(PasswordEquals constraintAnnotation) {
  20. this.min = constraintAnnotation.min();
  21. this.max = constraintAnnotation.max();
  22. }
  23. /**
  24. * 校驗引數
  25. * @param value
  26. * @param context
  27. * @return
  28. */
  29. @Override
  30. public boolean isValid(ValidEvt value, ConstraintValidatorContext context) {
  31. String password1 = value.getPassword1();
  32. String password2 = value.getPassword2();
  33. return password1.equals(password2) && this.validLength(password1, password2);
  34. }
  35. /**
  36. * 校驗密碼長度
  37. * @param password1
  38. * @param password2
  39. * @return
  40. */
  41. private boolean validLength(String password1, String password2) {
  42. return password1.length() > min && password1.length() < max
  43. && password2.length() > min && password2.length() < max;
  44. }
  45. }
  • 自定義註解校驗類
  1. /**
  2. * @Description 自定義註解校驗類
  3. * @author coisini
  4. * @date Aug 10, 2021
  5. * @Version 1.0
  6. */
  7. @Getter
  8. @Builder
  9. @PasswordEquals(min = 1, message = "Incorrect password length or passwords are not equal")
  10. public class ValidEvt {
  11. private String password1;
  12. private String password2;
  13. }
  • 引數驗證異常統一處理
  1. @ControllerAdvice
  2. public class GlobalExceptionAdvice {
  3. /**
  4. * 引數校驗異常處理器
  5. * @return
  6. */
  7. @ExceptionHandler(MethodArgumentNotValidException.class)
  8. @ResponseBody
  9. @ResponseStatus(HttpStatus.BAD_REQUEST)
  10. public UnifyMessage handleBeanValidation(HttpServletRequest request, MethodArgumentNotValidException e) {
  11. String method = request.getMethod();
  12. String requestUrl = request.getRequestURI();
  13. System.out.println(e);
  14. List<ObjectError> errors = e.getBindingResult().getAllErrors();
  15. String message = formatAllErrorMessages(errors);
  16. return new UnifyMessage(10001, message,method + " " + requestUrl);
  17. }
  18. /**
  19. * 自定義註解校驗異常處理器
  20. * @param req
  21. * @param e
  22. * @return
  23. */
  24. @ExceptionHandler(ConstraintViolationException.class)
  25. @ResponseBody
  26. @ResponseStatus(HttpStatus.BAD_REQUEST)
  27. public UnifyMessage handleConstrainException(HttpServletRequest req, ConstraintViolationException e){
  28. String method = req.getMethod();
  29. String requestUrl = req.getRequestURI();
  30. String message = e.getMessage();
  31. return new UnifyMessage(10001, message,method + " " + requestUrl);
  32. }
  33. /**
  34. * 異常訊息拼接
  35. * @param errors
  36. * @return
  37. */
  38. private String formatAllErrorMessages(List<ObjectError> errors){
  39. StringBuffer errorMsg = new StringBuffer();
  40. errors.forEach(error ->
  41. errorMsg.append(error.getDefaultMessage()).append(";")
  42. );
  43. return errorMsg.toString();
  44. }
  45. }
  • 統一訊息返回
  1. /**
  2. * @Description 統一訊息返回
  3. * @author coisini
  4. * @date Aug 9, 2021
  5. * @Version 1.0
  6. */
  7. public class UnifyMessage {
  8. private int code;
  9. private String message;
  10. private String requestUrl;
  11. public int getCode() {
  12. return code;
  13. }
  14. public String getMessage() {
  15. return message;
  16. }
  17. public String getRequestUrl() {
  18. return requestUrl;
  19. }
  20. public UnifyMessage(int code, String message, String requestUrl) {
  21. this.code = code;
  22. this.message = message;
  23. this.requestUrl = requestUrl;
  24. }
  25. }
  • 測試類
  1. @PostMapping(value = "/test1")
  2. public ValidEvt test1(@RequestBody @Validated ValidEvt evt){
  3. return evt;
  4. }
  • 測試結果

- End -



夢想是鹹魚
關注一下吧