1. 程式人生 > >SpringMVC引數校驗

SpringMVC引數校驗

****
> SpringMVC是根據引數的名字,然後用setter方法來對資料進行繫結的,若型別沒有匹配上則會出現400的錯誤,同時還要注意空值問題
## 1. 引數校驗 我們在做Web層的時候,接收了各種引數,儘管前端已經做了驗證,但難免惡意傳參,所以要對傳過來的資料保持不信任的態度來進行引數校驗
筆者日常進行驗證的方式如下: ```java @RequestMapping(value = "/create", method = RequestMethod.POST) public String createUser(String name, String email) { if(name == null || name.isEmpty()){ return "名字不能為空"; } if(email == null || email.isEmpty()){ // 這裡還要加上郵箱格式的驗證,省略省略 return "郵箱不能為空"; } } ``` 乍一看好像沒什麼問題,能夠應付需求,但是一旦引數多了起來就會像下面那樣
```java @RequestMapping(value = "/create", method = RequestMethod.POST) public String createUser(String name, String email, String sex, String password, String nickName, String address) { if(name == null || name.isEmpty()){ return "名字不能為空"; } if(email == null || email.isEmpty()){ return "郵箱不能為空"; } if(sex == null || sex.isEmpty()){ return "性別不能為空"; } if(password == null || password.isEmpty()){ return "密碼不能為空"; } if(address == null || address.isEmpty()){ return "地址不能為空"; } } ``` 這裡看還挺整齊的,一目瞭然,其實除了非空判斷還需各種格式驗證沒有列出了,**如果再新增引數**就成了累贅,一個類中引數校驗的程式碼就佔了大部分,得不償失
**這時候就該考慮簡便的引數校驗方式了——JSR-303(基於註解)**



## 2. JSR-303 JSR-303是一個被提出來的資料驗證**規範**,所以這僅僅是個介面,沒有具體實現的功能,**容易被誤解為JSR-303就是用於資料驗證的的工具**。我們要用到JSR-303的規範,那麼就需要匯入實現類的jar包,比如Hibernate Validator也是我們後面使用的jar包。
Spring也提供了引數校驗的方式,即實現其內部的validator介面來進行引數校驗,介面有兩個方法: ```java public class UserValidator implements Validator { // 判斷是否支援驗證該類 public boolean supports(Class clazz) { return User.class.equals(clazz); } // 校驗資料,將報錯資訊放入Error物件中 public void validate(Object obj, Errors e) { // ValidationUtils的靜態方法rejectIfEmpty(),對屬性進行非空判斷 ValidationUtils.rejectIfEmpty(e,"name","name.empty"); User user = (User)obj; if(user.getAge() < 0){ e.rejectValue("age", "年齡不能為負數"); } } } ```
我們當然不滿足那麼麻煩的方法,所以JSR-303出場
JSR-303是基於註解校驗的,註解已經實現了各種限制,我們可以將註解標記在需要校驗的類的屬性上,或是對應的setter方法上(筆者習慣標記在屬性上)
匯入Hibernate Validator依賴jar包,筆者使用maven工程 ```xml org.hibernate.validator
hibernate-validator 6.1.2.Final
```
hibernate-validator實現了JSR-303的所有功能,額外還提供了一些實用的註解。我們可以將其分成兩部分,一個是JSR-303規範中包含的,另一部分是hibernate額外提供的。下面的註解看解釋就能明白是什麼功能了
JSR-303規範 | **Annotation** | **Description** | | :---------------------------- | :------------------------------------------------------- | | `@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額外提供的 | **Constraint** | **詳細資訊** | | :------------- | :------------------------------------- | | `@Email` | 被註釋的元素必須是電子郵箱地址 | | `@Length` | 被註釋的字串的大小必須在指定的範圍內 | | `@NotEmpty` | 被註釋的字串的必須非空 | | `@Range` | 被註釋的元素必須在合適的範圍內 | ​



## 3. JSR-303的簡單使用
### 3.1 在需要校驗的屬性上標記註解 註解有個屬性message存放自定義的錯誤資訊
```java public class User { @NotNull(message = "名字不能為空") private String name; @Email(message = "郵箱格式錯誤") private String email; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } // 各種getter / setter / 構造器 } ```
### 3.2 開啟校驗 在Controller方法入參中需要校驗的引數前加入@Validated()表明需要校驗,後方要加@BindingResult接收錯誤資訊,若沒加即接收不了錯誤資訊會報錯(若使用了全域性異常處理則可以不加)。@Validated()和@BindingResult二者一前一後緊密相連的,中間不能有任何數值相隔。
```java @RequestMapping(value = "/create", method = RequestMethod.POST) public String createUser(@Validated() User user, BindingResult bindingResult) { // 判斷是否有錯 if (bindingResult.hasErrors()) { // 獲取欄位上的錯誤 FieldError errors = bindingResult.getFieldError(); // 輸出message資訊 return (errors.getDefaultMessage() + "\n"); } // dosomething } ```
### 3.3 補充 按上面的方法日常使用應該沒什麼問題了,資料校驗中還有**分組**與**自定義校驗**的知識點,這裡筆者就不做 (tou) 說明 (lan) 了



## 4. 筆者遇到的小插曲 我們知道前端傳參過來都是字串,經過Spring的型別轉換器轉換成為我們需要的型別才能正常使用,之前筆者沒有使用JSR-303規範來校驗引數的時候莫得發覺問題,但這也為現在埋下了坑
如果傳個整型呢? ```java public class User { @Min(value = 0, message = "不能為負數") private int id; // 各種getter / setter / 構造器 } ``` ```java @RequestMapping(value = "/list", method = RequestMethod.GET) public String listByPage(@Validated() User user, BindingResult bindingResult) { if (bindingResult.hasErrors()) { FieldError errors = bindingResult.getFieldError(); return (errors.getDefaultMessage() + "\n"); } // dosomething } ```
乍一看沒有什麼問題,普通使用能過去。**但是但是但是 int id 傳了空值就會報錯:** ``` Failed to convert property value of type 'java.lang.String' to required type 'int' for property 'id'; nested exception is java.lang.NumberFormatException: For input string: "" // 翻譯:轉換String到int id失敗,報錯原因是數字格式化異常,因為輸入了字串 “” ``` 這裡就是那個小小小的插曲,開始真是不知如何解決
### 解決方法 使用包裝類Integer,型別對不上就不匹配了,包裝類還會自動裝箱和拆箱,所以很方便解決空值問題 ```java // Integer id // 替換成包裝類之後傳的引數為,空值不接收即為null User{id=null, name='jiafu liu', email='[email protected]'} ```
### 教訓是:對於可能會傳空值的屬性一般會用包裝型別