­

SpringBoot – Bean validation 參數校驗

前言

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

  • 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 被注釋的元素必須在合適的範圍內

參數校驗的應用

依賴

<!-- hibernate validator-->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.2.4.Final</version>
</dependency>

簡單的參數校驗示例

  • 要想開啟參數校驗,需要在類上標註@Validated註解
  • 控制器
 @PostMapping(value = "/test")
 public ValidOneEvt param(@RequestBody @Validated ValidOneEvt evt){
     return evt;
 }
  • 實體驗證
@Getter
@Builder
public class ValidOneEvt {

    @NotEmpty(message = "名稱不能為空")
    private String name;

    private String sex;
    
}
  • 測試結果

在這裡插入圖片描述

級聯校驗

  • 級聯校驗需要在校驗的實體上添加@Valid。
  • 控制器
@PostMapping(value = "/test")
public ValidOneEvt param(@RequestBody @Validated ValidOneEvt evt){
    return evt;
}
  • 實體驗證
@Getter
@Builder
public class ValidOneEvt {

    @NotEmpty(message = "名稱不能為空")
    private String name;

    private String sex;

    @Valid
    private ValidTwoEvt validTwoEvt;

}
  • 級聯實體
@Getter
@Setter
public class ValidTwoEvt {

    @Length(min = 2)
    private String name;

}
  • 請求示例

在這裡插入圖片描述

  • 校驗結果

在這裡插入圖片描述

@Validated 與 @Valid

兩者具有相似性
註解地方

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

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

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

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

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

自定義校驗註解

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

  • 自定義效驗註解驗證密碼是否相等
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * @Description 自定義參數校驗註解
 *      @Documented 註解中的注釋加入文檔
 *      @Retention 註解保留階段 RetentionPolicy.RUNTIME 運行階段
 *      @Target 作用目標,註解的使用範圍 TYPE:用於描述類、介面(包括註解類型) 或enum聲明
 *      @Constraint 將註解和註解關聯類關聯到一起
 * @author coisini
 * @date Aug 10, 2021
 * @Version 1.0
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Constraint(validatedBy = PasswordValidator.class)
public @interface PasswordEquals {

    int min() default 4;

    int max() default 6;

    String message() default "passwords are not equal";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}
  • 自定義校驗註解關聯類
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * @Description 自定義校驗註解關聯類
 *      ConstraintValidator的第一個參數:註解的類型
 *      ConstraintValidator的第二個參數:自定義註解修飾的目標的類型
 * @author coisini
 * @date Aug 10, 2021
 * @Version 1.0
 */
public class PasswordValidator implements ConstraintValidator<PasswordEquals, ValidEvt> {

    private int min;
    private int max;

    /**
     * 初始化獲取註解參數
     * @param constraintAnnotation
     */
    @Override
    public void initialize(PasswordEquals constraintAnnotation) {
        this.min = constraintAnnotation.min();
        this.max = constraintAnnotation.max();
    }

    /**
     * 校驗參數
     * @param value
     * @param context
     * @return
     */
    @Override
    public boolean isValid(ValidEvt value, ConstraintValidatorContext context) {
        String password1 = value.getPassword1();
        String password2 = value.getPassword2();

        return password1.equals(password2) && this.validLength(password1, password2);
    }

    /**
     * 校驗密碼長度
     * @param password1
     * @param password2
     * @return
     */
    private boolean validLength(String password1, String password2) {
        return password1.length() > min && password1.length() < max
                && password2.length() > min && password2.length() < max;
    }

}
  • 自定義註解校驗類
/**
 * @Description 自定義註解校驗類
 * @author coisini
 * @date Aug 10, 2021
 * @Version 1.0
 */
@Getter
@Builder
@PasswordEquals(min = 1, message = "Incorrect password length or passwords are not equal")
public class ValidEvt {

    private String password1;
    private String password2;

}
  • 參數驗證異常統一處理
@ControllerAdvice
public class GlobalExceptionAdvice {
	/**
     * 參數校驗異常處理器
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public UnifyMessage handleBeanValidation(HttpServletRequest request, MethodArgumentNotValidException e) {
        String method = request.getMethod();
        String requestUrl = request.getRequestURI();
        System.out.println(e);

        List<ObjectError> errors = e.getBindingResult().getAllErrors();
        String message = formatAllErrorMessages(errors);

        return new UnifyMessage(10001, message,method + " " + requestUrl);
    }

    /**
     * 自定義註解校驗異常處理器
     * @param req
     * @param e
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public UnifyMessage handleConstrainException(HttpServletRequest req, ConstraintViolationException e){
        String method = req.getMethod();
        String requestUrl = req.getRequestURI();
        String message = e.getMessage();
        return new UnifyMessage(10001, message,method + " " + requestUrl);
    }

    /**
     * 異常消息拼接
     * @param errors
     * @return
     */
    private String formatAllErrorMessages(List<ObjectError> errors){
        StringBuffer errorMsg = new StringBuffer();
        errors.forEach(error ->
                errorMsg.append(error.getDefaultMessage()).append(";")
        );
        return errorMsg.toString();
    }
}
  • 統一消息返回
/**
 * @Description 統一消息返回
 * @author coisini
 * @date Aug 9, 2021
 * @Version 1.0
 */
public class UnifyMessage {

    private int code;
    private String message;
    private String requestUrl;

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public String getRequestUrl() {
        return requestUrl;
    }

    public UnifyMessage(int code, String message, String requestUrl) {
        this.code = code;
        this.message = message;
        this.requestUrl = requestUrl;
    }

}
  • 測試類
@PostMapping(value = "/test1")
public ValidEvt test1(@RequestBody @Validated ValidEvt evt){
    return evt;
}
  • 測試結果

在這裡插入圖片描述

在這裡插入圖片描述

– End –




夢想是鹹魚

關注一下吧