JSR 是 Java Specification Requests 的縮寫,即 Java 規範提案。
存在各類各樣的 JSR,簡單的理解爲 JSR 是一種 Java 標準。
JSR 303 就是數據檢驗的一個標準(Bean Validation (JSR 303))。
參考:
https://www.jianshu.com/p/554533f88370html
處理一段業務邏輯,首先要確保數據輸入的正確性,因此須要先對數據進行檢查,保證數據在語義上的正確性,再根據數據進行下一步的處理。
前端能夠經過 js 程序校驗數據是否合法,後端一樣也須要進行校驗。然後端最簡單的實現就是直接在業務方法中對數據進行處理,可是不一樣的業務方法可能會出現一樣的校驗操做,這樣就出現了數據的冗餘。爲了解決這個狀況,JSR 303 出現了。
JSR 303 使用 Bean Validation,即在 Bean 上添加相應的註解,去實現數據校驗。這樣在執行業務方法前,都會根據註解對數據進行校驗,從而減小自定義的校驗邏輯,減小代碼冗餘。前端
(1)能夠經過簡單的註解校驗 Bean 屬性,好比 @NotNull、@Null 等。
(2)能夠經過 Group 分組自定義須要執行校驗的屬性。
(3)能夠自定義註解並指定校驗規則。
(4)支持基於 JSR 303 的實現,好比 Hibernate Validator(額外添加一些註解)。java
(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
沒用 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
(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 測試。
(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 是否合法。
(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 會被捕獲。
註解 註解詳情 @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 格式