SpringMVC Validation 介紹 對于任何一個應用而言在客戶端做的數(shù)據有效性驗證都不是安全有效的,這時候就要求我們在開發(fā)的時候在服務端也對數(shù)據的有效性進行驗證。 SpringMVC 自身對數(shù)據在服務端的校驗有一個比較好的支持,它能將我們提交到服務端的數(shù)據按照我們事先的約定進行數(shù)據有效性驗證,對于不合格的數(shù)據信息 SpringMVC 會把它保存在錯誤對象中,這些錯誤信息我們也可以通過 SpringMVC 提供的標簽在前端 JSP 頁面上進行展示。 使用 Validator 接口進行驗證在 SpringMVC 中提供了一個 Validator 接口,我們可以通過該接口來定義我們自己對實體對象的驗證。接下來看一個示例。 假設我們現(xiàn)在有一個需要進行驗證的實體類 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; } }
那么當我們需要使用 SpringMVC 提供的 Validator 接口來對該實體類進行校驗的時候該如何做呢?這個時候我們應該提供一個 Validator 的實現(xiàn)類,并實現(xiàn) Validator 接口的 supports 方法和 validate 方法。 Supports 方法用于判斷當前的 Validator 實現(xiàn)類是否支持校驗當前需要校驗的實體類,只有當 supports 方法的返回結果為 true 的時候,該 Validator 接口實現(xiàn)類的 validate 方法才會被調用來對當前需要校驗的實體類進行校驗。這里假設我們需要驗證 User 類的 username 和 password 都不能為空,先給出其代碼,稍后再進行解釋。這里我們定義一個 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 對象進行校驗。在 validate 方法中我們校驗了 User 對象的 username 和 password 不為 empty 的情況,這里的 empty 包括 null 和空字符串兩種情況。 ValidationUtils 類是 Spring 中提供的一個工具類。 Errors 就是 Spring 用來存放錯誤信息的對象。 我們已經定義了一個對 User 類進行校驗的 UserValidator 了,但是這個時候 UserValidator 還不能對 User 對象進行校驗,因為我們還沒有告訴 Spring 應該使用 UserValidator 來校驗 User 對象。在 SpringMVC 中我們可以使用 DataBinder 來設定當前 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:/"; } }
在上面這段代碼中我們可以看到我們定義了一個 UserController ,該 Controller 有一個處理 login 操作的處理器方法 login ,它需要接收客戶端發(fā)送的一個 User 對象,我們就是要利用前面的 UserValidator 對該 User 對象進行校驗。首先我們可以看到我們 login 方法接收的參數(shù) user 是用 @Valid 進行標注的,這里的 @Valid 是定義在 JSR-303 標準中的,我這里使用的是 Hibernate Validation 對它的實現(xiàn)。這里我們 必須使用 @Valid 標注我們需要校驗的參數(shù) user ,否則 Spring 不會對它進行校驗。另外我們的 處理器方法必須給定包含 Errors 的參數(shù) ,這可以是 Errors 本身,也可以是它的子類 BindingResult ,使用了 Errors 參數(shù)就是告訴 Spring 關于表單對象數(shù)據校驗的錯誤將由我們自己來處理,否則 Spring 會直接拋出異常。前面有提到我們可以通過 DataBinder 來指定需要使用的 Validator ,我們可以看到在上面代碼中我們通過 @InitBinder 標記的方法 initBinder 設置了當前 Controller 需要使用的 Validator 是 UserValidator 。這樣當我們請求處理器方法 login 時就會使用 DataBinder 設定的 UserValidator 來校驗當前的表單對象 User ,首先會通過 UserValidator 的 supports 方法判斷其是否支持 User 對象的校驗,若支持則調用 UserValidator 的 validate 方法,并把相關的校驗信息存放到當前的 Errors 對象中。接著我們就可以在我們的處理器方法中根據是否有校驗異常信息來做不同的操作。在上面代碼中我們定義了在有異常信息的時候就跳轉到登陸頁面。這樣我們就可以在登陸頁面上通過 errors 標簽來展示這些錯誤信息了。 我們知道在 Controller 類中通過 @InitBinder 標記的方法只有在請求當前 Controller 的時候才會被執(zhí)行,所以其中定義的 Validator 也只能在當前 Controller 中使用,如果我們希望一個 Validator 對所有的 Controller 都起作用的話,我們可以通過 WebBindingInitializer 的 initBinder 方法來設定了。另外,在 SpringMVC 的配置文件中通過 mvc:annotation-driven 的 validator 屬性也可以指定全局的 Validator 。代碼如下所示: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www./schema/beans" xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:context="http://www./schema/context" xmlns:mvc="http://www./schema/mvc" xsi:schemaLocation="http://www./schema/beans http://www./schema/beans/spring-beans-3.0.xsd http://www./schema/context http://www./schema/context/spring-context-3.0.xsd http://www./schema/mvc http://www./schema/mvc/spring-mvc-3.0.xsd"> <mvc:annotation-driven validator="userValidator"/> <bean id="userValidator" class="com.xxx.xxx.UserValidator"/> ... </beans>
使用 JSR-303 Validation 進行驗證JSR-303 是一個數(shù)據驗證的規(guī)范,這里我不會講這個規(guī)范是怎么回事,只會講一下 JSR-303 在 SpringMVC 中的應用。 JSR-303 只是一個規(guī)范,而 Spring 也沒有對這一規(guī)范進行實現(xiàn),那么當我們在 SpringMVC 中需要使用到 JSR-303 的時候就需要我們提供一個對 JSR-303 規(guī)范的實現(xiàn), Hibernate Validator 是實現(xiàn)了這一規(guī)范的,這里我將以它作為 JSR-303 的實現(xiàn)來講解 SpringMVC 對 JSR-303 的支持。 JSR-303 的校驗是基于注解的,它內部已經定義好了一系列的限制注解,我們只需要把這些注解標記在需要驗證的實體類的屬性上或是其對應的 get 方法上。來看以下一個需要驗證的實體類 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 對應的 get 方法上都加上了一個注解,這些注解就是 JSR-303 里面定義的限制,其中 @NotBlank 是 Hibernate Validator 的擴展。不難發(fā)現(xiàn),使用 JSR-303 來進行校驗比使用 Spring 提供的 Validator 接口要簡單的多。我們知道注解只是起到一個標記性的作用,它是不會直接影響到代碼的運行的,它需要被某些類識別到才能起到限制作用。使用 SpringMVC 的時候我們只需要把 JSR-303 的實現(xiàn)者對應的 jar 包放到 classpath 中,然后在 SpringMVC 的配置文件中引入 MVC Namespace ,并加上 <mvn:annotation-driven/> 就可以非常方便的使用 JSR-303 來進行實體對象的驗證。加上了 <mvn:annotation-driven/> 之后 Spring 會自動檢測 classpath 下的 JSR-303 提供者并自動啟用對 JSR-303 的支持,把對應的校驗錯誤信息放到 Spring 的 Errors 對象中。這時候 SpringMVC 的配置文件如下所示: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www./schema/beans" xmlns:xsi="http://www./2001/XMLSchema-instance" xmlns:context="http://www./schema/context" xmlns:mvc="http://www./schema/mvc" xsi:schemaLocation="http://www./schema/beans http://www./schema/beans/spring-beans-3.0.xsd http://www./schema/context http://www./schema/context/spring-context-3.0.xsd http://www./schema/mvc http://www./schema/mvc/spring-mvc-3.0.xsd"> <mvc:annotation-driven/> </beans>
接著我們來定義一個使用 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:/"; } }
這樣當我們不帶任何參數(shù)請求 login.do 的時候就不能通過實體對象 User 的屬性數(shù)據有效性限制,然后會把對應的錯誤信息放置在當前的 Errors 對象中。
JSR-303 原生支持的限制有如下幾種 :
除了 JSR-303 原生支持的限制類型之外我們還可以定義自己的限制類型。定義自己的限制類型首先我們得定義一個該種限制類型的注解,而且該注解需要使用 @Constraint 標注。現(xiàn)在假設我們需要定義一個表示金額的限制類型,那么我們可以這樣定義: 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 {}; }
我們可以看到在上面代碼中我們定義了一個 Money 注解,而且該注解上標注了 @Constraint 注解,使用 @Constraint 注解標注表明我們定義了一個用于限制的注解。 @Constraint 注解的 validatedBy 屬性用于指定我們定義的當前限制類型需要被哪個 ConstraintValidator 進行校驗。在上面代碼中我們指定了 Money 限制類型的校驗類是 MoneyValidator 。 另外需要注意的是我們在定義自己的限制類型的注解時有三個屬性是必須定義的,如上面代碼所示的 message 、 groups 和 payload 屬性。 在定義了限制類型 Money 之后,接下來就是定義我們的限制類型校驗類 MoneyValidator 了。限制類型校驗類必須實現(xiàn)接口 javax.validation.ConstraintValidator ,并實現(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})?$";//表示金額的正則表達式 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 是使用了泛型的。它一共需要指定兩種類型,第一個類型是對應的 initialize 方法的參數(shù)類型,第二個類型是對應的 isValid 方法的第一個參數(shù)類型。從上面的兩個方法我們可以看出 isValid 方法是用于進行校驗的,有時候我們在校驗的過程中是需要取當前的限制類型的屬性來進行校驗的,比如我們在對 @Min 限制類型進行校驗的時候我們是需要通過其 value 屬性獲取到當前校驗類型定義的最小值的,我們可以看到 isValid 方法無法獲取到當前的限制類型 Money 。這個時候 initialize 方法的作用就出來了。我們知道 initialize 方法是可以獲取到當前的限制類型的,所以當我們在校驗某種限制類型時需要獲取當前限制類型的某種屬性的時候,我們可以給當前的 ConstraintValidator 定義對應的屬性,然后在 initialize 方法中給該屬性賦值,接下來我們就可以在 isValid 方法中使用其對應的屬性了。針對于這種情況我們來看一個代碼示例,現(xiàn)在假設我要定義自己的 @Min 限制類型和對應的 MinValidator 校驗器,那么我可以如下定義: 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 校驗器 public class MinValidator implements ConstraintValidator<Min, Integer> { private int minValue; public void initialize(Min min) { // TODO Auto-generated method stub //把Min限制類型的屬性value賦值給當前ConstraintValidator的成員變量minValue minValue = min.value(); } public boolean isValid(Integer value, ConstraintValidatorContext arg1) { // TODO Auto-generated method stub //在這里我們就可以通過當前ConstraintValidator的成員變量minValue訪問到當前限制類型Min的value屬性了 return value >= minValue; } }
繼續(xù)來說一下 ConstraintValidator 泛型的第二個類型 ,我們已經知道它的第二個類型是 對應的 isValid 的方法的第一個參數(shù) ,從我給的參數(shù)名稱 value 來看也可以知道 isValid 方法的第一個參數(shù)正是對應的當前需要校驗的數(shù)據的值,而它的類型也 正是對應的我們需要校驗的數(shù)據的數(shù)據類型。 這兩者的數(shù)據類型必須保持一致,否則 Spring 會提示找不到對應數(shù)據類型的 ConstraintValidator 。建立了自己的限制類型及其對應的 ConstraintValidator 后,其用法跟標準的 JSR-303 限制類型是一樣的。以下就是使用了上述自己定義的 JSR-303 限制類型—— Money 限制和 Min 限制的一個實體類: 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="標準的金額形式為xxx.xx") public Double getSalary() { return salary; } public void setSalary(Double salary) { this.salary = salary; } }
另外再講一點 Spring 對自定義 JSR-303 限制類型支持的新特性,那就是 Spring 支持往 ConstraintValidator 里面注入 bean 對象 。現(xiàn)在假設我們在 MoneyValidator 里面需要用到 Spring ApplicationContext 容器中的一個 UserController bean 對象,那么我們可以給 ConstraintValidator 定義一個 UserController 屬性,并給定其 set 方法,在 set 方法上加注解 @Resource 或 @Autowired 通過 set 方式來注入當前的 ApplicationContext 中擁有的 UserController bean 對象。關于 @Resource 和 @AutoWired 的區(qū)別可以參考 這篇博客 。所以我們可以這樣來定義我們的 MoneyValidator : public class MoneyValidator implements ConstraintValidator<Money, Double> { private String moneyReg = "^\\d+(\\.\\d{1,2})?$";//表示金額的正則表達式 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; } }
|
|
來自: 昵稱13039494 > 《springmvc》