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='1210911104@qq.com'}