SpringMVC參數校驗
- 2020 年 3 月 6 日
- 筆記
SpringMVC是根據參數的名字,然後用setter方法來對數據進行綁定的,若類型沒有匹配上則會出現400的錯誤,同時還要注意空值問題
1. 參數校驗
我們在做Web層的時候,接收了各種參數,儘管前端已經做了驗證,但難免惡意傳參,所以要對傳過來的數據保持不信任的態度來進行參數校驗
筆者日常進行驗證的方式如下:
@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 "郵箱不能為空"; } }
乍一看好像沒什麼問題,能夠應付需求,但是一旦參數多了起來就會像下面那樣
@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介面來進行參數校驗,介面有兩個方法:
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工程
<!-- 參數校驗 --> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.1.2.Final</version> </dependency>
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存放自定義的錯誤資訊
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二者一前一後緊密相連的,中間不能有任何數值相隔。
@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規範來校驗參數的時候莫得發覺問題,但這也為現在埋下了坑
如果傳個整型呢?
public class User { @Min(value = 0, message = "不能為負數") private int id; // 各種getter / setter / 構造器 }
@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,類型對不上就不匹配了,包裝類還會自動裝箱和拆箱,所以很方便解決空值問題
// Integer id // 替換成包裝類之後傳的參數為,空值不接收即為null User{id=null, name='jiafu liu', email='[email protected]'}


