SpringMVC介紹之Validation

對于任何一個(gè)應(yīng)用而言在客戶端做的數(shù)據(jù)有效性驗(yàn)證都不是安全有效的,這時(shí)候就要求我們在開發(fā)的時(shí)候在服務(wù)端也對數(shù)據(jù)的有效性進(jìn)行驗(yàn)證。SpringMVC自身對數(shù)據(jù)在服務(wù)端的校驗(yàn)有一個(gè)比較好的支持,它能將我們提交到服務(wù)端的數(shù)據(jù)按照我們事先的約定進(jìn)行數(shù)據(jù)有效性驗(yàn)證,對于不合格的數(shù)據(jù)信息SpringMVC會把它保存在錯(cuò)誤對象中,這些錯(cuò)誤信息我們也可以通過SpringMVC提供的標(biāo)簽在前端JSP頁面上進(jìn)行展示。

使用Validator接口進(jìn)行驗(yàn)證

在SpringMVC中提供了一個(gè)Validator接口,我們可以通過該接口來定義我們自己對實(shí)體對象的驗(yàn)證。接下來看一個(gè)示例。
假設(shè)我們現(xiàn)在有一個(gè)需要進(jìn)行驗(yàn)證的實(shí)體類User,其代碼如下所示:

public class User {  
   
    private String username;  
     
    private String password;  
   
    public String getUsername() {  
       return username;  
    }  
   
    public void setUsername(String username) {  
       this.username = username;  
    }  
   
    public String getPassword() {  
       return password;  
    }  
   
    public void setPassword(String password) {  
       this.password = password;  
    }  
     
    public String toString() {  
       return username + ", " + password;  
    }  
     
} 

那么當(dāng)我們需要使用SpringMVC提供的Validator接口來對該實(shí)體類進(jìn)行校驗(yàn)的時(shí)候該如何做呢?這個(gè)時(shí)候我們應(yīng)該提供一個(gè)Validator的實(shí)現(xiàn)類,并實(shí)現(xiàn)Validator接口的supports方法和validate方法。

Supports方法用于判斷當(dāng)前的Validator實(shí)現(xiàn)類是否支持校驗(yàn)當(dāng)前需要校驗(yàn)的實(shí)體類,只有當(dāng)supports方法的返回結(jié)果為true的時(shí)候,該Validator接口實(shí)現(xiàn)類的validate方法才會被調(diào)用來對當(dāng)前需要校驗(yàn)的實(shí)體類進(jìn)行校驗(yàn)。這里假設(shè)我們需要驗(yàn)證User類的username和password都不能為空,先給出其代碼,稍后再進(jìn)行解釋。這里我們定義一個(gè)UserValidator,其代碼如下:

import org.springframework.validation.Errors;  
import org.springframework.validation.ValidationUtils;  
import org.springframework.validation.Validator;  
   
public class UserValidator implements Validator {  
   
    public boolean supports(Class<?> clazz) {  
       // TODO Auto-generated method stub  
       return User.class.equals(clazz);  
    }  
   
    public void validate(Object obj, Errors errors) {  
       // TODO Auto-generated method stub  
       ValidationUtils.rejectIfEmpty(errors, "username", null, "Username is empty.");  
       User user = (User) obj;  
       if (null == user.getPassword() || "".equals(user.getPassword()))  
           errors.rejectValue("password", null, "Password is empty.");  
    }  
   
}  

在上述代碼中我們在supports方法中定義了該UserValidator只支持對User對象進(jìn)行校驗(yàn)。在validate方法中我們校驗(yàn)了User對象的username和password不為empty的情況,這里的empty包括null和空字符串兩種情況。ValidationUtils類是Spring中提供的一個(gè)工具類。Errors就是Spring用來存放錯(cuò)誤信息的對象。

我們已經(jīng)定義了一個(gè)對User類進(jìn)行校驗(yàn)的UserValidator了,但是這個(gè)時(shí)候UserValidator還不能對User對象進(jìn)行校驗(yàn),因?yàn)槲覀冞€沒有告訴Spring應(yīng)該使用UserValidator來校驗(yàn)User對象。在SpringMVC中我們可以使用DataBinder來設(shè)定當(dāng)前Controller需要使用的Validator。先來看下面一段代碼:

import javax.validation.Valid;  
import org.springframework.stereotype.Controller;  
import org.springframework.validation.BindingResult;  
import org.springframework.validation.DataBinder;  
import org.springframework.web.bind.annotation.InitBinder;  
import org.springframework.web.bind.annotation.RequestMapping;  
   
@Controller  
public class UserController {  
     
    @InitBinder  
    public void initBinder(DataBinder binder) {  
       binder.setValidator(new UserValidator());  
    }  
   
    @RequestMapping("login")  
    public String login(@Valid User user, BindingResult result) {  
       if (result.hasErrors())  
           return "redirect:user/login";  
       return "redirect:/";  
    }  
     
}

在上面這段代碼中我們可以看到我們定義了一個(gè)UserController,該Controller有一個(gè)處理login操作的處理器方法login,它需要接收客戶端發(fā)送的一個(gè)User對象,我們就是要利用前面的UserValidator對該User對象進(jìn)行校驗(yàn)。首先我們可以看到我們login方法接收的參數(shù)user是用@Valid進(jìn)行標(biāo)注的,這里的@Valid是定義在JSR-303標(biāo)準(zhǔn)中的,我這里使用的是Hibernate Validation對它的實(shí)現(xiàn)。這里我們必須使用@Valid標(biāo)注我們需要校驗(yàn)的參數(shù)user,否則Spring不會對它進(jìn)行校驗(yàn)。另外我們的處理器方法必須給定包含Errors的參數(shù),這可以是Errors本身,也可以是它的子類BindingResult,使用了Errors參數(shù)就是告訴Spring關(guān)于表單對象數(shù)據(jù)校驗(yàn)的錯(cuò)誤將由我們自己來處理,否則Spring會直接拋出異常,而且這個(gè)參數(shù)是必須緊挨著@Valid參數(shù)的,即必須緊挨著需要校驗(yàn)的參數(shù),這就意味著我們有多少個(gè)@Valid參數(shù)就需要有多少個(gè)對應(yīng)的Errors參數(shù),它們是一一對應(yīng)的。前面有提到我們可以通過DataBinder來指定需要使用的Validator,我們可以看到在上面代碼中我們通過@InitBinder標(biāo)記的方法initBinder設(shè)置了當(dāng)前Controller需要使用的Validator是UserValidator。這樣當(dāng)我們請求處理器方法login時(shí)就會使用DataBinder設(shè)定的UserValidator來校驗(yàn)當(dāng)前的表單對象User,首先會通過UserValidator的supports方法判斷其是否支持User對象的校驗(yàn),若支持則調(diào)用UserValidator的validate方法,并把相關(guān)的校驗(yàn)信息存放到當(dāng)前的Errors對象中。接著我們就可以在我們的處理器方法中根據(jù)是否有校驗(yàn)異常信息來做不同的操作。在上面代碼中我們定義了在有異常信息的時(shí)候就跳轉(zhuǎn)到登陸頁面。這樣我們就可以在登陸頁面上通過errors標(biāo)簽來展示這些錯(cuò)誤信息了。

我們知道在Controller類中通過@InitBinder標(biāo)記的方法只有在請求當(dāng)前Controller的時(shí)候才會被執(zhí)行,所以其中定義的Validator也只能在當(dāng)前Controller中使用,如果我們希望一個(gè)Validator對所有的Controller都起作用的話,我們可以通過WebBindingInitializer的initBinder方法來設(shè)定了。另外,在SpringMVC的配置文件中通過mvc:annotation-driven的validator屬性也可以指定全局的Validator。代碼如下所示:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:mvc="http://www.springframework.org/schema/mvc"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
     http://www.springframework.org/schema/context  
     http://www.springframework.org/schema/context/spring-context-3.0.xsd  
     http://www.springframework.org/schema/mvc  
     http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">  
      
    <mvc:annotation-driven validator="userValidator"/>  
     
    <bean id="userValidator" class="com.xxx.xxx.UserValidator"/>  
 
</beans> 

使用JSR-303 Validation進(jìn)行驗(yàn)證

JSR-303是一個(gè)數(shù)據(jù)驗(yàn)證的規(guī)范,這里我不會講這個(gè)規(guī)范是怎么回事,只會講一下JSR-303在SpringMVC中的應(yīng)用。JSR-303只是一個(gè)規(guī)范,而Spring也沒有對這一規(guī)范進(jìn)行實(shí)現(xiàn),那么當(dāng)我們在SpringMVC中需要使用到JSR-303的時(shí)候就需要我們提供一個(gè)對JSR-303規(guī)范的實(shí)現(xiàn),Hibernate Validator是實(shí)現(xiàn)了這一規(guī)范的,這里我將以它作為JSR-303的實(shí)現(xiàn)來講解SpringMVC對JSR-303的支持。

JSR-303的校驗(yàn)是基于注解的,它內(nèi)部已經(jīng)定義好了一系列的限制注解,我們只需要把這些注解標(biāo)記在需要驗(yàn)證的實(shí)體類的屬性上或是其對應(yīng)的get方法上。來看以下一個(gè)需要驗(yàn)證的實(shí)體類User的代碼:

import javax.validation.constraints.Min;  
import javax.validation.constraints.NotNull;  
import org.hibernate.validator.constraints.NotBlank;  
   
public class User {  
   
    private String username;  
     
    private String password;  
     
    private int age;  
   
    @NotBlank(message="用戶名不能為空")  
    public String getUsername() {  
       return username;  
    }  
   
    public void setUsername(String username) {  
       this.username = username;  
    }  
   
    @NotNull(message="密碼不能為null")  
    public String getPassword() {  
       return password;  
    }  
   
    public void setPassword(String password) {  
       this.password = password;  
    }  
   
    @Min(value=10, message="年齡的最小值為10")  
    public int getAge() {  
       return age;  
    }  
   
    public void setAge(int age) {  
       this.age = age;  
    }  
     
}  

我們可以看到我們在username、password和age對應(yīng)的get方法上都加上了一個(gè)注解,這些注解就是JSR-303里面定義的限制,其中@NotBlank是Hibernate Validator的擴(kuò)展。不難發(fā)現(xiàn),使用JSR-303來進(jìn)行校驗(yàn)比使用Spring提供的Validator接口要簡單的多。我們知道注解只是起到一個(gè)標(biāo)記性的作用,它是不會直接影響到代碼的運(yùn)行的,它需要被某些類識別到才能起到限制作用。

使用SpringMVC的時(shí)候我們只需要把JSR-303的實(shí)現(xiàn)者對應(yīng)的jar包放到classpath中,然后在SpringMVC的配置文件中引入MVC Namespace,并加上mvn:annotation-driven/就可以非常方便的使用JSR-303來進(jìn)行實(shí)體對象的驗(yàn)證。加上了mvn:annotation-driven/之后Spring會自動檢測classpath下的JSR-303提供者并自動啟用對JSR-303的支持,把對應(yīng)的校驗(yàn)錯(cuò)誤信息放到Spring的Errors對象中。這時(shí)候SpringMVC的配置文件如下所示:

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:mvc="http://www.springframework.org/schema/mvc"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  
     http://www.springframework.org/schema/context  
     http://www.springframework.org/schema/context/spring-context-3.0.xsd  
     http://www.springframework.org/schema/mvc  
     http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">  
      
    <mvc:annotation-driven/>  
</beans>  

接著我們來定義一個(gè)使用User對象作為參數(shù)接收者的Controller,其代碼如下所示:

import javax.validation.Valid;  
import org.springframework.stereotype.Controller;  
import org.springframework.validation.BindingResult;  
import org.springframework.web.bind.annotation.RequestMapping;  
   
@Controller  
public class UserController {  
   
    @RequestMapping("login")  
    public String login(@Valid User user, BindingResult result) {  
       if (result.hasErrors())  
           return "user/login";  
       return "redirect:/";  
    }  
     
}  

這樣當(dāng)我們不帶任何參數(shù)請求login.do的時(shí)候就不能通過實(shí)體對象User的屬性數(shù)據(jù)有效性限制,然后會把對應(yīng)的錯(cuò)誤信息放置在當(dāng)前的Errors對象中。

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

限制 說明
@Null 限制只能為null
@NotNull 限制必須不為null
@AssertFalse 限制必須為false
@AssertTrue 限制必須為true
@DecimalMax(value) 限制必須為一個(gè)不大于指定值的數(shù)字
@DecimalMin(value) 限制必須為一個(gè)不小于指定值的數(shù)字
@Digits(integer,fraction) 限制必須為一個(gè)小數(shù),且整數(shù)部分的位數(shù)不能超過integer,小數(shù)部分的位數(shù)不能超過fraction
@Future 限制必須是一個(gè)將來的日期
@Max(value) 限制必須為一個(gè)不大于指定值的數(shù)字
@Min(value) 限制必須為一個(gè)不小于指定值的數(shù)字
@Past 限制必須是一個(gè)過去的日期
@Pattern(value) 限制必須符合指定的正則表達(dá)式
@Size(max,min) 限制字符長度必須在min到max之間

除了JSR-303原生支持的限制類型之外我們還可以定義自己的限制類型。定義自己的限制類型首先我們得定義一個(gè)該種限制類型的注解,而且該注解需要使用@Constraint標(biāo)注?,F(xiàn)在假設(shè)我們需要定義一個(gè)表示金額的限制類型,那么我們可以這樣定義:

import java.lang.annotation.ElementType;  
import java.lang.annotation.Retention;  
import java.lang.annotation.RetentionPolicy;  
import java.lang.annotation.Target;  
   
import javax.validation.Constraint;  
import javax.validation.Payload;  
   
import com.xxx.xxx.constraint.impl.MoneyValidator;  
   
@Target({ElementType.FIELD, ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
@Constraint(validatedBy=MoneyValidator.class)  
public @interface Money {  
     
    String message() default"不是金額形式";  
     
    Class<?>[] groups() default {};  
     
    Class<? extends Payload>[] payload() default {};  
   
}  

我們可以看到在上面代碼中我們定義了一個(gè)Money注解,而且該注解上標(biāo)注了@Constraint注解,使用@Constraint注解標(biāo)注表明我們定義了一個(gè)用于限制的注解。@Constraint注解的validatedBy屬性用于指定我們定義的當(dāng)前限制類型需要被哪個(gè)ConstraintValidator進(jìn)行校驗(yàn)。在上面代碼中我們指定了Money限制類型的校驗(yàn)類是MoneyValidator。

另外需要注意的是我們在定義自己的限制類型的注解時(shí)有三個(gè)屬性是必須定義的,如上面代碼所示的messagegroupspayload屬性。

在定義了限制類型Money之后,接下來就是定義我們的限制類型校驗(yàn)類MoneyValidator了。限制類型校驗(yàn)類必須實(shí)現(xiàn)接口javax.validation.ConstraintValidator,并實(shí)現(xiàn)它的initialize和isValid方法。我們先來看一下MoneyValidator的代碼示例:

import java.util.regex.Pattern;  
   
import javax.validation.ConstraintValidator;  
import javax.validation.ConstraintValidatorContext;  
   
import com.xxx.xxx.constraint.Money;  
   
public class MoneyValidator implements ConstraintValidator<Money, Double> {  
   
    private String moneyReg = "^\\d+(\\.\\d{1,2})?$";//表示金額的正則表達(dá)式  
    private Pattern moneyPattern = Pattern.compile(moneyReg);  
     
    public void initialize(Money money) {  
       // TODO Auto-generated method stub  
        
    }  
   
    public boolean isValid(Double value, ConstraintValidatorContext arg1) {  
       // TODO Auto-generated method stub  
       if (value == null)  
           return true;  
       return moneyPattern.matcher(value.toString()).matches();  
    }  
   
}  

從上面代碼中我們可以看到ConstraintValidator是使用了泛型的。

它一共需要指定兩種類型:

  • 第一個(gè)類型是對應(yīng)的initialize方法的參數(shù)類型,
  • 第二個(gè)類型是對應(yīng)的isValid方法的第一個(gè)參數(shù)類型。

從上面的兩個(gè)方法我們可以看出isValid方法是用于進(jìn)行校驗(yàn)的,有時(shí)候我們在校驗(yàn)的過程中是需要取當(dāng)前的限制類型的屬性來進(jìn)行校驗(yàn)的,比如我們在對@Min限制類型進(jìn)行校驗(yàn)的時(shí)候我們是需要通過其value屬性獲取到當(dāng)前校驗(yàn)類型定義的最小值的,我們可以看到isValid方法無法獲取到當(dāng)前的限制類型Money。

這個(gè)時(shí)候initialize方法的作用就出來了。我們知道initialize方法是可以獲取到當(dāng)前的限制類型的,所以當(dāng)我們在校驗(yàn)?zāi)撤N限制類型時(shí)需要獲取當(dāng)前限制類型的某種屬性的時(shí)候,我們可以給當(dāng)前的ConstraintValidator定義對應(yīng)的屬性,然后在initialize方法中給該屬性賦值,接下來我們就可以在isValid方法中使用其對應(yīng)的屬性了。

針對于這種情況我們來看一個(gè)代碼示例,現(xiàn)在假設(shè)我要定義自己的@Min限制類型和對應(yīng)的MinValidator校驗(yàn)器,那么我可以如下定義:

Min限制類型

@Target({ElementType.FIELD, ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
@Constraint(validatedBy=MinValidator.class)  
public @interface Min {  
   
    int value() default 0;  
     
    String message();  
     
    Class<?>[] groups() default {};  
     
    Class<? extends Payload>[] payload() default {};  
}

MinValidator校驗(yàn)器

public class MinValidator implements ConstraintValidator<Min, Integer> {  
   
    private int minValue;  
     
    public void initialize(Min min) {  
       // TODO Auto-generated method stub  
       //把Min限制類型的屬性value賦值給當(dāng)前ConstraintValidator的成員變量minValue  
       minValue = min.value();  
    }  
   
    public boolean isValid(Integer value, ConstraintValidatorContext arg1) {  
       // TODO Auto-generated method stub  
       //在這里我們就可以通過當(dāng)前ConstraintValidator的成員變量minValue訪問到當(dāng)前限制類型Min的value屬性了  
       return value >= minValue;  
    }  
   
}  

繼續(xù)來說一下ConstraintValidator泛型的第二個(gè)類型,我們已經(jīng)知道它的第二個(gè)類型是對應(yīng)的isValid的方法的第一個(gè)參數(shù),從我給的參數(shù)名稱value來看也可以知道isValid方法的第一個(gè)參數(shù)正是對應(yīng)的當(dāng)前需要校驗(yàn)的數(shù)據(jù)的值,而它的類型也正是對應(yīng)的我們需要校驗(yàn)的數(shù)據(jù)的數(shù)據(jù)類型。

這兩者的數(shù)據(jù)類型必須保持一致,否則Spring會提示找不到對應(yīng)數(shù)據(jù)類型的ConstraintValidator。建立了自己的限制類型及其對應(yīng)的ConstraintValidator后,其用法跟標(biāo)準(zhǔn)的JSR-303限制類型是一樣的。

以下就是使用了上述自己定義的JSR-303限制類型——Money限制和Min限制的一個(gè)實(shí)體類:

public class User {  
     
    private int age;  
     
    private Double salary;  
   
    @Min(value=8, message="年齡不能小于8歲")  
    public int getAge() {  
       return age;  
    }  
   
    public void setAge(int age) {  
       this.age = age;  
    }  
   
    @Money(message="標(biāo)準(zhǔn)的金額形式為xxx.xx")  
    public Double getSalary() {  
       return salary;  
    }  
   
    public void setSalary(Double salary) {  
       this.salary = salary;  
    }  
     
}  

另外再講一點(diǎn)Spring對自定義JSR-303限制類型支持的新特性,那就是Spring支持往ConstraintValidator里面注入bean對象?,F(xiàn)在假設(shè)我們在MoneyValidator里面需要用到Spring ApplicationContext容器中的一個(gè)UserController bean對象,那么我們可以給ConstraintValidator定義一個(gè)UserController屬性,并給定其set方法,在set方法上加注解@Resource@Autowired通過set方式來注入當(dāng)前的ApplicationContext中擁有的UserController bean對象。關(guān)于@Resource@AutoWired的區(qū)別可以參考這篇博客 。

所以我們可以這樣來定義我們的MoneyValidator:

public class MoneyValidator implements ConstraintValidator<Money, Double> {  
   
    private String moneyReg = "^\\d+(\\.\\d{1,2})?$";//表示金額的正則表達(dá)式  
    private Pattern moneyPattern = Pattern.compile(moneyReg);  
    private UserController controller;  
     
    public void initialize(Money money) {  
       // TODO Auto-generated method stub  
        
    }  
   
    public boolean isValid(Double value, ConstraintValidatorContext arg1) {  
       // TODO Auto-generated method stub  
       System.out.println("UserController: .............." + controller);  
       if (value == null)  
           returntrue;  
       return moneyPattern.matcher(value.toString()).matches();  
    }  
   
    public UserController getController() {  
       return controller;  
    }  
   
    @Resource  
    public void setController(UserController controller) {  
       this.controller = controller;  
    }  
   
}  
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容