JSR 303 進行後臺數據校驗

1、JSR 303

一、什麼是 JSR 303?

  JSR 是 Java Specification Requests 的縮寫,即 Java 規範提案。
  存在各類各樣的 JSR,簡單的理解爲 JSR 是一種 Java 標準。
  JSR 303 就是數據檢驗的一個標準(Bean Validation (JSR 303))。
參考:
  https://www.jianshu.com/p/554533f88370html

二、爲何使用 JSR 303?

  處理一段業務邏輯,首先要確保數據輸入的正確性,因此須要先對數據進行檢查,保證數據在語義上的正確性,再根據數據進行下一步的處理。
  前端能夠經過 js 程序校驗數據是否合法,後端一樣也須要進行校驗。然後端最簡單的實現就是直接在業務方法中對數據進行處理,可是不一樣的業務方法可能會出現一樣的校驗操做,這樣就出現了數據的冗餘。爲了解決這個狀況,JSR 303 出現了。
  JSR 303 使用 Bean Validation,即在 Bean 上添加相應的註解,去實現數據校驗。這樣在執行業務方法前,都會根據註解對數據進行校驗,從而減小自定義的校驗邏輯,減小代碼冗餘。前端

三、JSR 303 常見操做?

(1)能夠經過簡單的註解校驗 Bean 屬性,好比 @NotNull、@Null 等。
(2)能夠經過 Group 分組自定義須要執行校驗的屬性。
(3)能夠自定義註解並指定校驗規則。
(4)支持基於 JSR 303 的實現,好比 Hibernate Validator(額外添加一些註解)。java

 

2、演示 JSR303 的簡單使用

一、構建一個 SpringBoot 項目用來演示

(1)構建一個 SpringBoot 項目,以及使用 EasyCode 逆向生成相關的代碼。
參考地址:
  https://www.cnblogs.com/l-y-h/p/12781586.html
模板代碼地址:
  https://gitee.com/lyh-man/fast-template.gitmysql

(2)工具使用詳情:
  SpringBoot 2.2.6 + JDK 1.8 + mysql 1.8 搭建基本開發環境
  IDEA + EasyCode + Lombok 插件 逆向生成基本代碼
  Postman 發送請求,測試接口git

二、未使用 JSR303 相關注解時

  沒用 JSR 303 相關注解時,須要手動在業務方法裏寫處理數據的邏輯。
  修改 Controller ,簡單測試一下未使用 JSR 303 相關注解時的作法。正則表達式

@RestController
@RequestMapping("api")
public class EmpController {
    @Resource
    private EmpService empService;

    @PostMapping("/emp")
    public Result createEmp(@RequestBody Emp emp) {
        if (emp.getId() == null || emp.getName() == null) {
            return Result.error().message("數據不存在");
        }
        return Result.ok().data("items", emp).message("數據插入成功");
    }
}

 

 

 

使用 postman 測試該接口,當 id 不存在時,會被檢測到。sql

 

 

 

id,name 都存在時,不會被捕獲。後端

 

 

 

  這裏只是簡單的測試一下邏輯,真實的數據檢測確定比這複雜的多,而後每一個方法都須要寫不一樣的數據處理邏輯,這樣就會形成數據的冗餘。而使用 JSR303 的相關注解,就很簡單,繼續往下看。api

 

三、使用 JSR 303 相關注解處理邏輯

(1)使用步驟:
Step1:
  在相關的 Bean 上標註須要處理的註解,並指定須要提示的信息(若不指定,會從默認配置文件中讀取默認的信息)。數組

Step2:
  在相關的方法上,使用 @Valid 註解(或者 @Validated 指定組名)標記須要被校驗的數據,不然會不生效。
注意:
  檢測到數據異常後,系統會向外拋出異常,若是作了統一異常處理,能夠根據 postman 測試的結果,找到控制檯打印出的 相應的異常,並處理。

Step3:
  處理異常。使用 BindingResult 能夠獲取到檢測結果,而後進行處理。
  也可使用 全局統一異常 處理(@RestControllerAdvice 與 @ExceptionHandler),處理檢測結果。
注:
  統一異常處理參考:https://www.cnblogs.com/l-y-h/p/12781586.html#_label2

 

(2)使用:
Step1:
  在相關的 Bean 上標註註解,並寫上指定信息。

import lombok.Data;

import javax.validation.constraints.NotNull;
import java.io.Serializable;

@Data
public class Emp implements Serializable {
    private static final long serialVersionUID = 281903912367009575L;

    @NotNull(message = "id 不能爲 null")
    private Integer id;

    @NotNull(message = "name 不能爲 null")
    private String name;
    
    private Double salary;
    
    private Integer age;
    
    private String email;
}

 

 

Step2:
  修改 Controller 方法,使用 @Valid 註解標記須要檢測的數據。

@RestController
@RequestMapping("api")
public class EmpController {
    @Resource
    private EmpService empService;

    @PostMapping("/emp")
    public Result createEmp(@Valid @RequestBody Emp emp) {
        return Result.ok().data("items", emp).message("數據插入成功");
    }
}

 

 

Step3:
  使用 postman 測試一下。會拋出 MethodArgumentNotValidException 異常。

 

 

控制檯打印的信息:

 

 

Step4:
  可使用 BindingResult 去處理捕獲到的數據並進行相關處理。

@RestController
@RequestMapping("api")
public class EmpController {
    @Resource
    private EmpService empService;

    @PostMapping("/emp")
    public Result createEmp(@Valid @RequestBody Emp emp, BindingResult result) {
        if (result.hasErrors()) {
            Map<String, String> map = new HashMap<>();
            // 獲取校驗結果,遍歷獲取捕獲到的每一個校驗結果
            result.getFieldErrors().forEach(item ->{
                // 獲取校驗的信息
                String message = item.getDefaultMessage();
                String field = item.getField();
                // 存儲獲得的校驗結果
                map.put(field, message);
            });
            return Result.error().message("數據不合法").data("items", map);
        }
        return Result.ok().data("items", emp).message("數據插入成功");
    }
}

 

 

使用 Postman 測試。

 

 

Step5:
  經過上面的步驟,已經能夠捕獲異常、處理異常,可是每次都是在業務方法中手動處理邏輯,這樣的實現,代碼確定會冗餘。能夠將其抽出,使用 統一異常處理,每次異常發生時,將其捕獲。
  根據 Step3 能夠看到會拋出 MethodArgumentNotValidException 異常,因此須要將其捕獲。
  須要使用 @RestControllerAdvice 與 @ExceptionHandler。

@RestControllerAdvice
public class GlobalExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handlerValidException(MethodArgumentNotValidException e) {
        logger.error(e.getMessage(), e);
        BindingResult result = e.getBindingResult();
        Map<String, String> map = new HashMap<>();
        // 獲取校驗結果,遍歷獲取捕獲到的每一個校驗結果
        result.getFieldErrors().forEach(item ->{
            // 存儲獲得的校驗結果
            map.put(item.getField(), item.getDefaultMessage());
        });
        return Result.error().message("數據校驗不合法").data("items", map);
    }
}

 

 

相應的業務方法裏,不須要再用 BindingResult 去處理數據了(即 Step2 的狀態)。

@RestController
@RequestMapping("api")
public class EmpController {
    @Resource
    private EmpService empService;

    @PostMapping("/emp")
    public Result createEmp(@Valid @RequestBody Emp emp) {
        return Result.ok().data("items", emp).message("數據插入成功");
    }
}

 

 

使用 Postman 測試。

 

 

四、JSR 303 分組校驗

(1)爲何使用 分組校驗?
  經過上面的過程,能夠了解到單個方法的校驗規則。
  若是出現多個方法,都須要校驗 Bean,且校驗規則不一樣的時候,怎麼辦呢?
  分組校驗就能夠去解決該問題,每一個分組指定不一樣的校驗規則,不一樣的方法執行不一樣的分組,就能夠獲得不一樣的校驗結果。

(2)基本認識
  JSR 303 的每一個註解都默認具有三個屬性:
    message 用來定義數據校驗失敗後的提示消息,默認讀取配置文件的內容。
      全局搜索 ValidationMessages.properties,能夠看到默認的信息。

    groups 用來定義分組,其是一個 class 數組,能夠指定多個分組。

String message() default "{javax.validation.constraints.NotNull.message}";

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

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

 

(3)使用分組步驟:
Step1:
  定義一個空接口,用於指定分組,內部不須要任何實現。

Step2:
  指定 註解時,經過 groups 指定分組。用於指定在某個分組條件下,纔去執行校驗規則。

Step3:
  在相關的業務方法上,經過 @Validated 註解指定分組,去指定校驗。
注:
  使用分組校驗後,Bean 註解上若不指定分組,則不會執行校驗規則。

(4)使用:
Step1:
  建立分組接口。
  建立兩個分組接口 AddGroup、UpdateGroup。
其中:
  AddGroup 用於指定 添加數據 時的校驗規則(好比:id、name 均不爲 null)。
  UpdateGroup 用於指定 修改數據 時的校驗規則(好比:name 不容許爲 null)。

 

 

Step2:
  給 Bean 添加註解,並指定分組信息。

@Data
public class Emp implements Serializable {
    private static final long serialVersionUID = 281903912367009575L;

    @NotNull(message = "id 不能爲 null", groups = {AddGroup.class})
    private Integer id;

    @NotNull(message = "name 不能爲 null", groups = {AddGroup.class, UpdateGroup.class})
    private String name;

    private Double salary;

    private Integer age;

    private String email;
}

 

 

Step3:
  在業務方法上,經過 @Validated 註解指定分組,去指定校驗。
以下例,定義兩個方法,Post 請求會觸發 createEmp 方法,Put 請求會觸發 UpdateEmp 方法。

@RestController
@RequestMapping("api")
public class EmpController {
    @Resource
    private EmpService empService;

    @PostMapping("/emp")
    public Result createEmp(@Validated({AddGroup.class}) @RequestBody Emp emp) {
        return Result.ok().data("items", emp).message("數據插入成功");
    }

    @PutMapping("/emp")
    public Result UpdateEmp(@Validated({UpdateGroup.class}) @RequestBody Emp emp) {
        return Result.ok().data("items", emp).message("數據插入成功");
    }
}

 

 

Step4:
  使用 Postman 測試,發送 Post 請求,觸發 createEmp 方法,執行 AddGroup 校驗規則。
  檢測 id、name 是否合法。

 

 

發送 Put 請求,觸發 UpdateEmp 方法,執行 UpdateGroup 校驗規則。
只檢測 name 是否合法。

 

 

五、JSR 303 自定義校驗註解

(1)爲何使用自定義校驗註解?
  上面的註解知足不了業務需求時,能夠自定義校驗註解,自定義校驗規則。

(2)步驟:
Step1:
  須要自定義一個校驗註解。
  能夠建立一個 ValidationMessages.properties 用於保存默認的 message 信息。

Step2:
  須要自定義一個校驗器,即自定義校驗規則。
  實現 ConstraintValidator 接口,並重寫相關方法。
注:
  initialize 方法用於初始化,能夠獲取 自定義的屬性的值。
  isValid 方法用於校驗,能夠獲取到實際的值,而後與自定義的屬性值進行比較。

Step3:
  將校驗註解 與 校驗器 關聯起來。
  @Constraint(validatedBy = {TestValidConstraintValidator.class})

(3)使用:
  以下例,自定義一個校驗規則,判斷數據長度是否合法。
  默認爲 String 屬性,當 String 爲 Null 或者 長度大於 5 時,校驗不經過。
  能夠自定義 長度。
Step1:
  自定義一個校驗註解,@TestValid,用於判斷一個值的長度是否合法。

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 用於判斷一個值的長度是否合法
 */
@Target({FIELD})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {TestValidConstraintValidator.class})
public @interface TestValid {
    String message() default "{com.lyh.test.TestValid.message}";

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

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

    /**
     * 返回一個長度
     * @return 默認爲 5
     */
    int length() default 5;
}

 

 

配置文件內容:

 

 

Step2:
  自定義一個校驗器TestValidConstraintValidator, 用於檢測值是否合法。

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 實現 ConstraintValidator 接口,
 * 其中 ConstraintValidator 的泛型,一個須要指定自定義的註解,一個須要指定須要獲取的值的類型。
 * 好比:
 *  ConstraintValidator<TestValid, String> 中
 *      TestValid   表示自定義註解
 *      String      表示獲取的值的類型
 * 即定義規則,判斷一個 String 的值的長度是否知足條件
 */
public class TestValidConstraintValidator implements ConstraintValidator<TestValid, String> {

    /**
     * 用於保存自定義的(默認)長度
     */
    private int length;

    /**
     * 初始化方法,獲取默認數據
     * @param constraintAnnotation 註解對象
     */
    @Override
    public void initialize(TestValid constraintAnnotation) {
        length = constraintAnnotation.length();
    }

    /**
     * 自定義校驗規則,若是 String 爲 Null 或者 長度大於 5,則校驗失敗(返回 false)
     * @param value 須要校驗的值
     * @param context
     * @return true 表示校驗成功,false 表示校驗失敗
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value == null ? false : length > value.length();
    }

}

 

 

Step3:
  使用註解。

@Data
public class Emp implements Serializable {
    private static final long serialVersionUID = 281903912367009575L;

    @NotNull(message = "id 不能爲 null", groups = {AddGroup.class})
    private Integer id;

    @TestValid(groups = {AddGroup.class})
    @NotNull(message = "name 不能爲 null", groups = {AddGroup.class, UpdateGroup.class})
    private String name;

    private Double salary;

    private Integer age;

    @TestValid(length = 10, message = "值不能爲 Null 且長度不超過 10", groups = {AddGroup.class})
    private String email;
}

 

 

使用 Postman 測試。
  name、email 都不存在時,會被捕獲數據異常。

 

 

name 數據不合法、email 數據合法時,name 會被捕獲。

 

 

3、JSR 303 相關注解

一、空檢查相關注解

註解                      註解詳情
@Null                  被指定的註解元素必須爲 Null
@NotNull               任意類型,不能爲 Null,但能夠爲空,好比空數組、空字符串。
@NotBlank              針對字符串,不能爲 Null,且去除先後空格後的字符串長度要大於 0。
@NotEmpty              針對字符串、集合、數組,不能爲 Null,且長度要大於 0。

 

 

 

 

 

 

 

 

二、長度檢查

註解                      註解詳情
@Size                   針對字符串、集合、數組,判斷長度是否在給定範圍內。
@Length                 針對字符串,判斷長度是否在給定範圍內。

 

 

 

 

三、布爾值檢查

註解                      註解詳情
@AssertTrue             針對布爾值,用來判斷布爾值是否爲 true
@AssertFalse            針對布爾值,用來判斷布爾值是否爲 false

 

 

 

 

四、日期檢查

註解                      註解詳情
@Past                  針對日期,用來判斷當前日期是否爲 過去的日期
@Future                針對日期,用來判斷當前日期是否爲 將來的日期

 

五、數值檢查

註解                      註解詳情
@Max(value)         針對字符串、數值,用來判斷是否小於等於某個指定值
@Min(value)         針對字符串、數值,用來判斷是否大於等於某個指定值

 

 

 

 

六、其餘

註解                      註解詳情
@Pattern             驗證字符串是否知足正則表達式
@Email               驗證字符串是否知足郵件格式
@Url                 驗證是否知足 url 格式

相關文章
相關標籤/搜索