SpringMVC介紹之Validation

對於任何一個應用而言在客戶端做的數據有效性驗證都不是安全有效的,這時候就要求我們在開發的時候在服務端也對數據的有效性進行驗證。SpringMVC自身對數據在服務端的校驗有一個比較好的支持,它能將我們提交到服務端的數據按照我們事先的約定進行數據有效性驗證,對於不合格的數據信息SpringMVC會把它保存在錯誤對象中,這些錯誤信息我們也可以通過SpringMVC提供的標籤在前端JSP頁面上進行展示。

使用Validator接口進行驗證

       SpringMVC中提供了一個Validator接口,我們可以通過該接口來定義我們自己對實體對象的驗證。接下來看一個示例。

       假設我們現在有一個需要進行驗證的實體類User,其代碼如下所示:

Java代碼  收藏代碼
  1. public class User {  
  2.    
  3.     private String username;  
  4.      
  5.     private String password;  
  6.    
  7.     public String getUsername() {  
  8.        return username;  
  9.     }  
  10.    
  11.     public void setUsername(String username) {  
  12.        this.username = username;  
  13.     }  
  14.    
  15.     public String getPassword() {  
  16.        return password;  
  17.     }  
  18.    
  19.     public void setPassword(String password) {  
  20.        this.password = password;  
  21.     }  
  22.      
  23.     public String toString() {  
  24.        return username + ", " + password;  
  25.     }  
  26.      
  27. }  

 

       那麼當我們需要使用SpringMVC提供的Validator接口來對該實體類進行校驗的時候該如何做呢?這個時候我們應該提供一個Validator的實現類,並實現Validator接口的supports方法和validate方法。Supports方法用於判斷當前的Validator實現類是否支持校驗當前需要校驗的實體類,只有當supports方法的返回結果爲true的時候,該Validator接口實現類的validate方法纔會被調用來對當前需要校驗的實體類進行校驗。這裏假設我們需要驗證User類的usernamepassword都不能爲空,先給出其代碼,稍後再進行解釋。這裏我們定義一個UserValidator,其代碼如下:

Java代碼  收藏代碼
  1. import org.springframework.validation.Errors;  
  2. import org.springframework.validation.ValidationUtils;  
  3. import org.springframework.validation.Validator;  
  4.    
  5. public class UserValidator implements Validator {  
  6.    
  7.     public boolean supports(Class<?> clazz) {  
  8.        // TODO Auto-generated method stub  
  9.        return User.class.equals(clazz);  
  10.     }  
  11.    
  12.     public void validate(Object obj, Errors errors) {  
  13.        // TODO Auto-generated method stub  
  14.        ValidationUtils.rejectIfEmpty(errors, "username"null"Username is empty.");  
  15.        User user = (User) obj;  
  16.        if (null == user.getPassword() || "".equals(user.getPassword()))  
  17.            errors.rejectValue("password"null"Password is empty.");  
  18.     }  
  19.    
  20. }  

 

       在上述代碼中我們在supports方法中定義了該UserValidator只支持對User對象進行校驗。在validate方法中我們校驗了User對象的usernamepassword不爲empty的情況,這裏的empty包括null和空字符串兩種情況。ValidationUtils類是Spring中提供的一個工具類。Errors就是Spring用來存放錯誤信息的對象。

       我們已經定義了一個對User類進行校驗的UserValidator了,但是這個時候UserValidator還不能對User對象進行校驗,因爲我們還沒有告訴Spring應該使用UserValidator來校驗User對象。在SpringMVC中我們可以使用DataBinder來設定當前Controller需要使用的Validator。先來看下面一段代碼:

 

Java代碼  收藏代碼
  1. import javax.validation.Valid;  
  2. import org.springframework.stereotype.Controller;  
  3. import org.springframework.validation.BindingResult;  
  4. import org.springframework.validation.DataBinder;  
  5. import org.springframework.web.bind.annotation.InitBinder;  
  6. import org.springframework.web.bind.annotation.RequestMapping;  
  7.    
  8. @Controller  
  9. public class UserController {  
  10.      
  11.     @InitBinder  
  12.     public void initBinder(DataBinder binder) {  
  13.        binder.setValidator(new UserValidator());  
  14.     }  
  15.    
  16.     @RequestMapping("login")  
  17.     public String login(@Valid User user, BindingResult result) {  
  18.        if (result.hasErrors())  
  19.            return "redirect:user/login";  
  20.        return "redirect:/";  
  21.     }  
  22.      
  23. }  

 

       在上面這段代碼中我們可以看到我們定義了一個UserController,該Controller有一個處理login操作的處理器方法login,它需要接收客戶端發送的一個User對象,我們就是要利用前面的UserValidator對該User對象進行校驗。首先我們可以看到我們login方法接收的參數user是用@Valid進行標註的,這裏的@Valid是定義在JSR-303標準中的,我這裏使用的是Hibernate Validation對它的實現。這裏我們必須使用@Valid標註我們需要校驗的參數user,否則Spring不會對它進行校驗。另外我們的處理器方法必須給定包含Errors的參數,這可以是Errors本身,也可以是它的子類BindingResult,使用了Errors參數就是告訴Spring關於表單對象數據校驗的錯誤將由我們自己來處理,否則Spring會直接拋出異常,而且這個參數是必須緊挨着@Valid參數的,即必須緊挨着需要校驗的參數,這就意味着我們有多少個@Valid參數就需要有多少個對應的Errors參數,它們是一一對應的。前面有提到我們可以通過DataBinder來指定需要使用的Validator,我們可以看到在上面代碼中我們通過@InitBinder標記的方法initBinder設置了當前Controller需要使用的ValidatorUserValidator。這樣當我們請求處理器方法login時就會使用DataBinder設定的UserValidator來校驗當前的表單對象User,首先會通過UserValidatorsupports方法判斷其是否支持User對象的校驗,若支持則調用UserValidatorvalidate方法,並把相關的校驗信息存放到當前的Errors對象中。接着我們就可以在我們的處理器方法中根據是否有校驗異常信息來做不同的操作。在上面代碼中我們定義了在有異常信息的時候就跳轉到登陸頁面。這樣我們就可以在登陸頁面上通過errors標籤來展示這些錯誤信息了。

       我們知道在Controller類中通過@InitBinder標記的方法只有在請求當前Controller的時候纔會被執行,所以其中定義的Validator也只能在當前Controller中使用,如果我們希望一個Validator對所有的Controller都起作用的話,我們可以通過WebBindingInitializerinitBinder方法來設定了。另外,在SpringMVC的配置文件中通過mvc:annotation-drivenvalidator屬性也可以指定全局的Validator。代碼如下所示:

Xml代碼  收藏代碼
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"  
  4.     xmlns:mvc="http://www.springframework.org/schema/mvc"  
  5.     xsi:schemaLocation="http://www.springframework.org/schema/beans  
  6.      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  7.      http://www.springframework.org/schema/context  
  8.      http://www.springframework.org/schema/context/spring-context-3.0.xsd  
  9.      http://www.springframework.org/schema/mvc  
  10.      http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">  
  11.       
  12.     <mvc:annotation-driven validator="userValidator"/>  
  13.      
  14.     <bean id="userValidator" class="com.xxx.xxx.UserValidator"/>  
  15.    
  16.     ...  
  17. </beans>  

 

      

使用JSR-303 Validation進行驗證

       JSR-303是一個數據驗證的規範,這裏我不會講這個規範是怎麼回事,只會講一下JSR-303SpringMVC中的應用。JSR-303只是一個規範,而Spring也沒有對這一規範進行實現,那麼當我們在SpringMVC中需要使用到JSR-303的時候就需要我們提供一個對JSR-303規範的實現,Hibernate Validator是實現了這一規範的,這裏我將以它作爲JSR-303的實現來講解SpringMVCJSR-303的支持。

       JSR-303的校驗是基於註解的,它內部已經定義好了一系列的限制註解,我們只需要把這些註解標記在需要驗證的實體類的屬性上或是其對應的get方法上。來看以下一個需要驗證的實體類User的代碼:

Java代碼  收藏代碼
  1.    
  2. import javax.validation.constraints.Min;  
  3. import javax.validation.constraints.NotNull;  
  4. import org.hibernate.validator.constraints.NotBlank;  
  5.    
  6. public class User {  
  7.    
  8.     private String username;  
  9.      
  10.     private String password;  
  11.      
  12.     private int age;  
  13.    
  14.     @NotBlank(message="用戶名不能爲空")  
  15.     public String getUsername() {  
  16.        return username;  
  17.     }  
  18.    
  19.     public void setUsername(String username) {  
  20.        this.username = username;  
  21.     }  
  22.    
  23.     @NotNull(message="密碼不能爲null")  
  24.     public String getPassword() {  
  25.        return password;  
  26.     }  
  27.    
  28.     public void setPassword(String password) {  
  29.        this.password = password;  
  30.     }  
  31.    
  32.     @Min(value=10, message="年齡的最小值爲10")  
  33.     public int getAge() {  
  34.        return age;  
  35.     }  
  36.    
  37.     public void setAge(int age) {  
  38.        this.age = age;  
  39.     }  
  40.      
  41. }  

 

       我們可以看到我們在usernamepasswordage對應的get方法上都加上了一個註解,這些註解就是JSR-303裏面定義的限制,其中@NotBlankHibernate Validator的擴展。不難發現,使用JSR-303來進行校驗比使用Spring提供的Validator接口要簡單的多。我們知道註解只是起到一個標記性的作用,它是不會直接影響到代碼的運行的,它需要被某些類識別到才能起到限制作用。使用SpringMVC的時候我們只需要把JSR-303的實現者對應的jar包放到classpath中,然後在SpringMVC的配置文件中引入MVC Namespace,並加上<mvn:annotation-driven/>就可以非常方便的使用JSR-303來進行實體對象的驗證。加上了<mvn:annotation-driven/>之後Spring會自動檢測classpath下的JSR-303提供者並自動啓用對JSR-303的支持,把對應的校驗錯誤信息放到SpringErrors對象中。這時候SpringMVC的配置文件如下所示:

Xml代碼  收藏代碼
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns="http://www.springframework.org/schema/beans"  
  3.     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"  
  4.     xmlns:mvc="http://www.springframework.org/schema/mvc"  
  5.     xsi:schemaLocation="http://www.springframework.org/schema/beans  
  6.      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
  7.      http://www.springframework.org/schema/context  
  8.      http://www.springframework.org/schema/context/spring-context-3.0.xsd  
  9.      http://www.springframework.org/schema/mvc  
  10.      http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">  
  11.       
  12.     <mvc:annotation-driven/>  
  13. </beans>  

 

       接着我們來定義一個使用User對象作爲參數接收者的Controller,其代碼如下所示:

Java代碼  收藏代碼
  1. import javax.validation.Valid;  
  2. import org.springframework.stereotype.Controller;  
  3. import org.springframework.validation.BindingResult;  
  4. import org.springframework.web.bind.annotation.RequestMapping;  
  5.    
  6. @Controller  
  7. public class UserController {  
  8.    
  9.     @RequestMapping("login")  
  10.     public String login(@Valid User user, BindingResult result) {  
  11.        if (result.hasErrors())  
  12.            return "user/login";  
  13.        return "redirect:/";  
  14.     }  
  15.      
  16. }  

 

       這樣當我們不帶任何參數請求login.do的時候就不能通過實體對象User的屬性數據有效性限制,然後會把對應的錯誤信息放置在當前的Errors對象中。

 

JSR-303原生支持的限制有如下幾種

限制

說明

@Null

限制只能爲null

@NotNull

限制必須不爲null

@AssertFalse

限制必須爲false

@AssertTrue

限制必須爲true

@DecimalMax(value)

限制必須爲一個不大於指定值的數字

@DecimalMin(value)

限制必須爲一個不小於指定值的數字

@Digits(integer,fraction)

限制必須爲一個小數,且整數部分的位數不能超過integer,小數部分的位數不能超過fraction

@Future

限制必須是一個將來的日期

@Max(value)

限制必須爲一個不大於指定值的數字

@Min(value)

限制必須爲一個不小於指定值的數字

@Past

限制必須是一個過去的日期

@Pattern(value)

限制必須符合指定的正則表達式

@Size(max,min)

限制字符長度必須在minmax之間

 

       除了JSR-303原生支持的限制類型之外我們還可以定義自己的限制類型。定義自己的限制類型首先我們得定義一個該種限制類型的註解,而且該註解需要使用@Constraint標註。現在假設我們需要定義一個表示金額的限制類型,那麼我們可以這樣定義:

Java代碼  收藏代碼
  1.    
  2. import java.lang.annotation.ElementType;  
  3. import java.lang.annotation.Retention;  
  4. import java.lang.annotation.RetentionPolicy;  
  5. import java.lang.annotation.Target;  
  6.    
  7. import javax.validation.Constraint;  
  8. import javax.validation.Payload;  
  9.    
  10. import com.xxx.xxx.constraint.impl.MoneyValidator;  
  11.    
  12. @Target({ElementType.FIELD, ElementType.METHOD})  
  13. @Retention(RetentionPolicy.RUNTIME)  
  14. @Constraint(validatedBy=MoneyValidator.class)  
  15. public @interface Money {  
  16.      
  17.     String message() default"不是金額形式";  
  18.      
  19.     Class<?>[] groups() default {};  
  20.      
  21.     Class<? extends Payload>[] payload() default {};  
  22.    
  23. }  

 

       我們可以看到在上面代碼中我們定義了一個Money註解,而且該註解上標註了@Constraint註解,使用@Constraint註解標註表明我們定義了一個用於限制的註解。@Constraint註解的validatedBy屬性用於指定我們定義的當前限制類型需要被哪個ConstraintValidator進行校驗。在上面代碼中我們指定了Money限制類型的校驗類是MoneyValidator另外需要注意的是我們在定義自己的限制類型的註解時有三個屬性是必須定義的,如上面代碼所示的messagegroupspayload屬性。

       在定義了限制類型Money之後,接下來就是定義我們的限制類型校驗類MoneyValidator了。限制類型校驗類必須實現接口javax.validation.ConstraintValidator,並實現它的initializeisValid方法。我們先來看一下MoneyValidator的代碼示例:

Java代碼  收藏代碼
  1.    
  2. import java.util.regex.Pattern;  
  3.    
  4. import javax.validation.ConstraintValidator;  
  5. import javax.validation.ConstraintValidatorContext;  
  6.    
  7. import com.xxx.xxx.constraint.Money;  
  8.    
  9. public class MoneyValidator implements ConstraintValidator<Money, Double> {  
  10.    
  11.     private String moneyReg = "^\\d+(\\.\\d{1,2})?$";//表示金額的正則表達式  
  12.     private Pattern moneyPattern = Pattern.compile(moneyReg);  
  13.      
  14.     public void initialize(Money money) {  
  15.        // TODO Auto-generated method stub  
  16.         
  17.     }  
  18.    
  19.     public boolean isValid(Double value, ConstraintValidatorContext arg1) {  
  20.        // TODO Auto-generated method stub  
  21.        if (value == null)  
  22.            return true;  
  23.        return moneyPattern.matcher(value.toString()).matches();  
  24.     }  
  25.    
  26. }  

 

       從上面代碼中我們可以看到ConstraintValidator是使用了泛型的。它一共需要指定兩種類型,第一個類型是對應的initialize方法的參數類型,第二個類型是對應的isValid方法的第一個參數類型。從上面的兩個方法我們可以看出isValid方法是用於進行校驗的,有時候我們在校驗的過程中是需要取當前的限制類型的屬性來進行校驗的,比如我們在對@Min限制類型進行校驗的時候我們是需要通過其value屬性獲取到當前校驗類型定義的最小值的,我們可以看到isValid方法無法獲取到當前的限制類型Money。這個時候initialize方法的作用就出來了。我們知道initialize方法是可以獲取到當前的限制類型的,所以當我們在校驗某種限制類型時需要獲取當前限制類型的某種屬性的時候,我們可以給當前的ConstraintValidator定義對應的屬性,然後在initialize方法中給該屬性賦值,接下來我們就可以在isValid方法中使用其對應的屬性了。針對於這種情況我們來看一個代碼示例,現在假設我要定義自己的@Min限制類型和對應的MinValidator校驗器,那麼我可以如下定義:

Min限制類型

Java代碼  收藏代碼
  1. @Target({ElementType.FIELD, ElementType.METHOD})  
  2. @Retention(RetentionPolicy.RUNTIME)  
  3. @Constraint(validatedBy=MinValidator.class)  
  4. public @interface Min {  
  5.    
  6.     int value() default 0;  
  7.      
  8.     String message();  
  9.      
  10.     Class<?>[] groups() default {};  
  11.      
  12.     Class<? extends Payload>[] payload() default {};  
  13. }  

 

 

MinValidator校驗器

Java代碼  收藏代碼
  1. public class MinValidator implements ConstraintValidator<Min, Integer> {  
  2.    
  3.     private int minValue;  
  4.      
  5.     public void initialize(Min min) {  
  6.        // TODO Auto-generated method stub  
  7.        //把Min限制類型的屬性value賦值給當前ConstraintValidator的成員變量minValue  
  8.        minValue = min.value();  
  9.     }  
  10.    
  11.     public boolean isValid(Integer value, ConstraintValidatorContext arg1) {  
  12.        // TODO Auto-generated method stub  
  13.        //在這裏我們就可以通過當前ConstraintValidator的成員變量minValue訪問到當前限制類型Min的value屬性了  
  14.        return value >= minValue;  
  15.     }  
  16.    
  17. }  

 

 

       繼續來說一下ConstraintValidator泛型的第二個類型,我們已經知道它的第二個類型是對應的isValid的方法的第一個參數,從我給的參數名稱value來看也可以知道isValid方法的第一個參數正是對應的當前需要校驗的數據的值,而它的類型也正是對應的我們需要校驗的數據的數據類型。這兩者的數據類型必須保持一致,否則Spring會提示找不到對應數據類型的ConstraintValidator。建立了自己的限制類型及其對應的ConstraintValidator後,其用法跟標準的JSR-303限制類型是一樣的。以下就是使用了上述自己定義的JSR-303限制類型——Money限制和Min限制的一個實體類:

Java代碼  收藏代碼
  1. public class User {  
  2.      
  3.     private int age;  
  4.      
  5.     private Double salary;  
  6.    
  7.     @Min(value=8, message="年齡不能小於8歲")  
  8.     public int getAge() {  
  9.        return age;  
  10.     }  
  11.    
  12.     public void setAge(int age) {  
  13.        this.age = age;  
  14.     }  
  15.    
  16.     @Money(message="標準的金額形式爲xxx.xx")  
  17.     public Double getSalary() {  
  18.        return salary;  
  19.     }  
  20.    
  21.     public void setSalary(Double salary) {  
  22.        this.salary = salary;  
  23.     }  
  24.      
  25. }  

 

 

       另外再講一點Spring對自定義JSR-303限制類型支持的新特性,那就是Spring支持往ConstraintValidator裏面注入bean對象。現在假設我們在MoneyValidator裏面需要用到Spring ApplicationContext容器中的一個UserController bean對象,那麼我們可以給ConstraintValidator定義一個UserController屬性,並給定其set方法,在set方法上加註解@Resource@Autowired通過set方式來注入當前的ApplicationContext中擁有的UserController bean對象。關於@Resource@AutoWired的區別可以參考這篇博客。所以我們可以這樣來定義我們的MoneyValidator

Java代碼  收藏代碼
  1.    
  2. public class MoneyValidator implements ConstraintValidator<Money, Double> {  
  3.    
  4.     private String moneyReg = "^\\d+(\\.\\d{1,2})?$";//表示金額的正則表達式  
  5.     private Pattern moneyPattern = Pattern.compile(moneyReg);  
  6.     private UserController controller;  
  7.      
  8.     public void initialize(Money money) {  
  9.        // TODO Auto-generated method stub  
  10.         
  11.     }  
  12.    
  13.     public boolean isValid(Double value, ConstraintValidatorContext arg1) {  
  14.        // TODO Auto-generated method stub  
  15.        System.out.println("UserController: .............." + controller);  
  16.        if (value == null)  
  17.            returntrue;  
  18.        return moneyPattern.matcher(value.toString()).matches();  
  19.     }  
  20.    
  21.     public UserController getController() {  
  22.        return controller;  
  23.     }  
  24.    
  25.     @Resource  
  26.     public void setController(UserController controller) {  
  27.        this.controller = controller;  
  28.     }  
  29.    
  30. }  

 

@Resource和@Autowire的區別 
在java代碼中可以使用@Autowire或者@Resource註解方式進行裝配,這兩個註解的區別是:
@Autowire默認按照類型裝配,默認情況下它要求依賴對象必須存在如果允許爲null,可以設置它required屬性爲false,如果我們想使用按照名稱裝配,可以結合@Qualifier註解一起使用;


@Resource默認按照名稱裝配,當找不到與名稱匹配的bean纔會按照類型裝配,可以通過name屬性指定,如果沒有指定name屬性,當註解標註在字段上,即默認取字段的名稱作爲bean名稱尋找依賴對象,當註解標註在屬性的setter方法上,即默認取屬性名作爲bean名稱尋找依賴對象.

注意:如果沒有指定name屬性,並且按照默認的名稱仍然找不到依賴的對象時候,會回退到按照類型裝配,但一旦指定了name屬性,就只能按照名稱裝配了.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章