SpringMVC參數校驗


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]'}

教訓是:對於可能會傳空值的屬性一般會用包裝類型