SpringBoot參數校驗的方法總結
一、前言
在上一篇MyBatis-plus 初體驗 中已經簡單實現瞭 MyBatis-Plus 數據庫查詢。我們知道 CURD 離不開前後端的數據交互,因此參數校驗是必不可少的。這篇主要講一下 SpringBoot 參數校驗。
在 Web 開發中經常需要對前端傳過來的參數進行校驗,例如格式校驗、非空校驗等,基本上每個接口都需要進行校驗。如果使用常規的 IF ELSE
進行校驗,隨著參數越來越多,校驗邏輯的冗餘度也越來越高,導致維護性變差。在 Java 中定義瞭一套基於註解的數據校驗規范 Bean Validation ,通過一些簡單的註解就能完成必要的邏輯校驗,相對來說就方便瞭很多。而 Bean Validation 隻是規范,並沒有具體的實現,Hibernate 提供瞭具體的實現,也即 Hibernate Validator ,這個也是目前使用得比較多的驗證器瞭。
二、註解介紹
validator 內置註解
@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 附加的 constraint
@Email
被註釋的元素必須是電子郵箱地址@Length
被註釋的字符串的大小必須在指定的范圍內@NotEmpty
被註釋的字符串的必須非空@Range
被註釋的元素必須在合適的范圍內@NotBlank
驗證字符串非null
,且長度必須大於0
註意
@NotNull
用於驗證對象是否不為null
,無法檢測長度為0的字符串;@NotEmpty
用於 String、Map 或者數組等集合類型不能為null
且長度必須大於0;@NotBlank
隻能用於String,不能為null
,且調用trim()
後,長度必須大於0;
校驗字符串是否為空,使用
@NotNull
,隻有參數不傳的時候才會檢測到,傳瞭空值(例如空字符串)仍然可以通過校驗,因此應該使用@NotBlank
三、添加依賴
在 SpringBoot
中 Bean Validation 已經集成在 starter-web
中,因此無需再添加依賴。但是本人實際測試發現,直接使用好像不行,因此添加瞭如下依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
四、創建用於校驗的實體類
創建一個 validator 目錄,在裡面創建一個 UserDTO
類:
本來想直接復用之前創建的 entity 類,但是後來想瞭下,entity 用於建立數據庫的映射關系,字段跟數據表是一一對應的,而這裡的 validator 是用於校驗前端傳過來的參數,字段跟前端傳的參數是對應的,因此不能復用,需要單獨寫一個 validator 類。
順便提一下,在 RestController 中使用自己定義的對象,需要有 setter、getter 之類的方法,或者使用 lombok 的 @Data 註解。如果不加的話會報錯:
No converter found for return value of type: class validator.UserDTO
使用 getter、setter 方法如下:
public class UserDTO { private String username; private Integer age; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
使用 lombok 的 @Data
註解如下,代碼與上面等效:
@Data public class UserDTO { private String username; private Integer age; }
五、寫一個測試用的接口
添加一個 POST 接口,從請求體中獲取參數,然後原封不動返回過去(主要是用來測試參數校驗的,這裡接口邏輯並不重要)
@PostMapping("validateUser") public UserDTO userValidate(@RequestBody UserDTO userDTO) { return userDTO; }
六、在實體類中添加註解
給需要校驗的參數添加註解:
@Data public class UserDTO { @NotBlank(message = "用戶名不能為空") private String username; @NotBlank(message = "手機號不能為空") private String mobile; @NotNull(message = "性別不能為空") private Integer sex; @NotNull(message = "年齡不能為空") private Integer age; @NotBlank(message = "郵箱不能為空") @Email(message = "郵箱格式錯誤") private String email; }
七、在 controller 方法中添加 Validated 註解
然後需要在 controller
方法體添加 @Validated
,不加 @Validated
校驗會不起作用。
用下面的數據測試一下:
{ "username": "", "mobile": "2333", "sex": 0, "age": 0, "email": "[email protected]" }
可以看到校驗是成功瞭,但是後臺拋瞭一個異常:
Validation failed for argument [0] in public validator.UserDTO com.hhlnyfz.hhlnyfz.HelloController.userValidate(validator.UserDTO): [Field error in object ‘userDTO’ on field ‘username’: rejected value []; codes [NotBlank.userDTO.username,NotBlank.username,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [userDTO.username,username]; arguments []; default message [username]]; default message [用戶名不能為空]] ]
然後返回參數並不理想,前端也並不容易處理返回參數:
八、添加全局異常處理
上面這種情況需要添加一下全局異常處理,這樣比較規范。創建一個 GlobalExceptionHandler
類,在類的上面添加 @RestControllerAdvice
註解,然後添加如下代碼:
/** * 全局異常處理類 */ @RestControllerAdvice public class GlobalExceptionHandler { // 捕獲 MethodArgumentNotValidException 異常 @ExceptionHandler(value = MethodArgumentNotValidException.class) public HashMap<String, Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) { HashMap<String, Object> map = new HashMap<>(); map.put("code", 400); map.put("msg", e.getMessage()); map.put("url", request.getRequestURL()); return map; } // 其他異常處理方法 }
這邊 @ExceptionHandler
註解中的 MethodArgumentNotValidException.class
用於捕獲請求參數異常。如果是 Exception.class
表示捕獲全部異常。不要用一個方法處理所有的異常,而是一個方法處理一種異常。如果需要處理其他異常,可以在下面添加方法。
還是用剛才的測試用例,這次異常被捕獲到瞭,返回的內容如下:
可以看到 e.getMessage()
把整個錯誤堆棧信息全部打印出來瞭,但我們隻需要把最後的 default message
返回給前端就行,因此改用 e.getBindingResult().getFieldError().getDefaultMessage()
,然後 IDE 給瞭提示:
Method invocation ‘getDefaultMessage’ may produce ‘NullPointerException’
也就是說 e.getBindingResult().getFieldError()
可能會是一個空指針 null
,於是按照 IDE 的提示用 Objects.requireNonNull
包裹一下,最終代碼如下:
/** * 全局異常處理類 */ @RestControllerAdvice public class GlobalExceptionHandler { // 捕獲 MethodArgumentNotValidException 異常 @ExceptionHandler(value = MethodArgumentNotValidException.class) public HashMap<String, Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) { HashMap<String, Object> map = new HashMap<>(); map.put("code", 400); map.put("msg", Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage()); map.put("url", request.getRequestURL()); return map; } // 其他異常處理方法 }
響應內容如下:
當然這邊還是有不規范的地方,沒有用統一響應體進行返回,後面會介紹如何封裝統一響應體。
九、分組校驗
@Valid
和 @Validated
兩個註解都可以實現校驗,前面的功能用 @Valid
也是可以的,但是 @Validated
功能更強大,可以實現分組校驗。什麼是分組校驗,分組校驗實際上實現瞭實體類的復用,有時候並不希望對所有的參數都進行校驗,例如下面這個情況:
@Data public class Route { @NotNull(message = "始發地省id不能為空") private Integer startProvinceId; @NotNull(message = "目的地省id不能為空") private Integer endProvinceId; @NotBlank(message = "詳細地址不能為空") private String address; }
假如在一個接口中隻希望校驗 startProvinceId
和 address
,而在另一個接口中隻希望校驗 endProvinceId
和 address
,這個時候就可以用分組校驗。可以定義一個接口:
public interface ValidateGroup { interface RouteValidStart {} interface RouteValidEnd {} }
然後在實體類中添加分組:
@Data public class Route { @NotNull(groups = {RouteValidStart.class}, message = "始發地省id不能為空") private Integer startProvinceId; @NotNull(groups = {RouteValidEnd.class}, message = "目的地省id不能為空") private Integer endProvinceId; @NotBlank(groups = {RouteValidStart.class, RouteValidEnd.class}, message = "詳細地址不能為空") private String address; }
然後在校驗的時候隻需要把分組傳入 @Validate
就可以實現指定參數的校驗:
@RequestMapping("addRoute") public ServerResponse addRoute(@RequestBody @Validated({RouteValidStart.class}) Route route) { // ... return ServerResponse.success(); }
十、單個參數校驗
在參數前面加上註解即可:
@PostMapping("/get") public ReturnVO getUserInfo(@RequestParam("userId") @NotNull(message = "用戶ID不能為空") String userId){ return new ReturnVO().success(); }
然後在 Controller 類上面增加 @Validated 註解,註意不是增加在參數前面。
到此這篇關於SpringBoot參數校驗的方法總結的文章就介紹到這瞭,更多相關SpringBoot參數校驗內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- SpringBoot參數校驗的最佳實戰教程
- Spring 使用Validation 驗證框架的問題詳解
- SpringBoot實現接口的各種參數校驗的示例
- 如何使用Bean Validation 解決業務中參數校驗
- SpringBoot集成Validation參數校驗