7.4、數據驗證
7.4.1、編程式數據驗證
Spring 2.x提供了編程式驗證支持,詳見【4.16.2 數據驗證】章節,在此我們重寫【4.16.2.4.1、編程式驗證器】一節示例。
(1、驗證器實現
複製cn.javass.chapter4.web.controller.support.validator.UserModelValidator
到cn.javass.chapter7.web.controller.support.validator.UserModelValidator。
(2、控制器實現
- @Controller
- public class RegisterSimpleFormController {
- private UserModelValidator validator = new UserModelValidator();
- @ModelAttribute("user") //① 暴露表單引用對象爲模型數據
- public UserModel getUser() {
- return new UserModel();
- }
- @RequestMapping(value = "/validator", method = RequestMethod.GET)
- public String showRegisterForm() { //② 表單展示
- return "validate/registerAndValidator";
- }
- @RequestMapping(value = "/validator", method = RequestMethod.POST)
- public String submitForm(
- @ModelAttribute("user") UserModel user,
- Errors errors) { //③ 表單提交
- validator.validate(user, errors); //1 調用UserModelValidator的validate方法進行驗證
- if(errors.hasErrors()) { //2如果有錯誤再回到表單展示頁面
- return showRegisterForm();
- }
- return "redirect:/success";
- }
- }
在submitForm方法中,我們首先調用之前寫的UserModelValidator的validate方法進行驗證,當然此處可以直接驗證並通過Errors接口來保留錯誤;此處還通過 Errors接口的hasErrors方法來決定當驗證失敗時顯示的錯誤頁面。
(3、spring配置文件chapter7-servlet.xml
(4、錯誤碼配置(messages.properties),需要執行NativeToAscii
直接將【springmvc-chapter4】項目中src下的messages.properties複製到src目錄下。
在spring配置文件chapter7-servlet.xml中添加
messageSource:
- <bean id="messageSource"
- class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
- <property name="basename" value="classpath:messages"/>
- <property name="fileEncodings" value="utf-8"/>
- <property name="cacheSeconds" value="120"/>
- </bean>
(5、視圖頁面(/WEB-INF/jsp/registerAndValidator.jsp)
直接將【springmvc-chapter4】項目中的/WEB-INF/jsp/registerAndValidator.jsp複製到當前項目下的/WEB-INF/jsp/validate/registerAndValidator.jsp。
(6、啓動服務器測試:
在瀏覽器地址欄輸入http://localhost:9080/springmvc-chapter7/validator進行測試,測試步驟和【4.16.2.4.1、編程式驗證器】一樣。
其他編程式驗證的使用,請參考【4.16.2 數據驗證】章節。
7.4.2、聲明式數據驗證
Spring3開始支持JSR-303驗證框架,JSR-303支持XML風格的和註解風格的驗證,接下來我們首先看一下如何和Spring集成。
7.4.2.1、集成
(1、添加jar包:
此處使用Hibernate-validator實現(版本:hibernate-validator-4.3.0.Final-dist.zip),將如下jar包添加到classpath(WEB-INF/lib下即可):
dist/hibernate-validator-4.3.0.Final.jar Hibernate 參考實現
(2、在Spring配置總添加對JSR-303驗證框架的支持
- <!-- 以下 validator ConversionService 在使用 mvc:annotation-driven 會 自動註冊-->
- <bean id="validator"
- class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
- <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
- <!-- 如果不加默認到 使用classpath下的 ValidationMessages.properties -->
- <property name="validationMessageSource" ref="messageSource"/>
- </bean>
此處使用Hibernate validator實現:
validationMessageSource屬性:指定國際化錯誤消息從哪裏取,此處使用之前定義的messageSource來獲取國際化消息;如果此處不指定該屬性,則默認到classpath下的ValidationMessages.properties取國際化錯誤消息。
通過ConfigurableWebBindingInitializer註冊validator:
- <bean id="webBindingInitializer"
- class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
- <property name="conversionService" ref="conversionService"/>
- <property name="validator" ref="validator"/>
- </bean>
其他配置和之前學習7.2.2.4一節一樣。
如上集成過程看起來比較麻煩,後邊我們會介紹<mvc:annotation-driven>和@EnableWebMvc,ConversionService會自動註冊,後續章節再詳細介紹。
(3、使用JSR-303驗證框架註解爲模型對象指定驗證信息
- package cn.javass.chapter7.model;
- import javax.validation.constraints.NotNull;
- public class UserModel {
- @NotNull(message="{username.not.empty}")
- private String username;
- }
通過@NotNull指定此username字段不允許爲空,當驗證失敗時將從之前指定的messageSource中獲取“username.not.empty”對於的錯誤信息,此處只有通過“{錯誤消息鍵值}”格式指定的才能從messageSource獲取。
(4、控制器
- package cn.javass.chapter7.web.controller.validate;
- //省略import
- @Controller
- public class HelloWorldController {
- @RequestMapping("/validate/hello")
- public String validate(@Valid @ModelAttribute("user") UserModel user, Errors errors) {
- if(errors.hasErrors()) {
- return "validate/error";
- }
- return "redirect:/success";
- }
- }
通過在命令對象上註解@Valid來告訴Spring MVC此命令對象在綁定完畢後需要進行JSR-303驗證,如果驗證失敗會將錯誤信息添加到errors錯誤對象中。
(5、驗證失敗後需要展示的頁面(/WEB-INF/jsp/validate/error.jsp)
- <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
- <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
- <form:form commandName="user">
- <form:errors path="*" cssStyle="color:red"></form:errors><br/>
- </form:form>
(6、測試
在瀏覽器地址欄中輸入http://localhost:9080/springmvc-chapter7/validate/hello,即沒有username數據,請求後將直接到驗證失敗界面並顯示錯誤消息“用戶名不能爲空”,如果請求時帶上“?username=zhang”將重定向到成功頁面。
到此集成就完成,接下來我們詳細學習下有哪些驗證約束註解吧。
7.4.2.2、內置的驗證約束註解
內置的驗證約束註解如下表所示(摘自hibernate validator reference):
驗證註解 |
驗證的數據類型 |
說明 |
@AssertFalse |
Boolean,boolean |
驗證註解的元素值是false |
@AssertTrue |
Boolean,boolean |
驗證註解的元素值是true |
@NotNull |
任意類型 |
驗證註解的元素值不是null |
@Null |
任意類型 |
驗證註解的元素值是null |
@Min(value=值) |
BigDecimal,BigInteger, byte, short, int, long,等任何Number或CharSequence(存儲的是數字)子類型 |
驗證註解的元素值大於等於@Min指定的value值 |
@Max(value=值) |
和@Min要求一樣 |
驗證註解的元素值小於等於@Max指定的value值 |
@DecimalMin(value=值) |
和@Min要求一樣 |
驗證註解的元素值大於等於@ DecimalMin指定的value值 |
@DecimalMax(value=值) |
和@Min要求一樣 |
驗證註解的元素值小於等於@ DecimalMax指定的value值 |
@Digits(integer=整數位數, fraction=小數位數) |
和@Min要求一樣 |
驗證註解的元素值的整數位數和小數位數上限 |
@Size(min=下限, max=上限) |
字符串、Collection、Map、數組等 |
驗證註解的元素值的在min和max(包含)指定區間之內,如字符長度、集合大小 |
@Past |
java.util.Date, java.util.Calendar; Joda Time類庫的日期類型 |
驗證註解的元素值(日期類型)比當前時間早 |
@Future |
與@Past要求一樣 |
驗證註解的元素值(日期類型)比當前時間晚 |
@NotBlank |
CharSequence子類型 |
驗證註解的元素值不爲空(不爲null、去除首位空格後長度爲0),不同於@NotEmpty,@NotBlank只應用於字符串且在比較時會去除字符串的首位空格 |
@Length(min=下限, max=上限) |
CharSequence子類型 |
驗證註解的元素值長度在min和max區間內 |
@NotEmpty |
CharSequence子類型、Collection、Map、數組 |
驗證註解的元素值不爲null且不爲空(字符串長度不爲0、集合大小不爲0) |
@Range(min=最小值, max=最大值) |
BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子類型和包裝類型 |
驗證註解的元素值在最小值和最大值之間 |
@Email(regexp=正則表達式, flag=標誌的模式) |
CharSequence子類型(如String) |
驗證註解的元素值是Email,也可以通過regexp和flag指定自定義的email格式 |
@Pattern(regexp=正則表達式, flag=標誌的模式) |
String,任何CharSequence的子類型 |
驗證註解的元素值與指定的正則表達式匹配 |
@Valid |
任何非原子類型 |
指定遞歸驗證關聯的對象; 如用戶對象中有個地址對象屬性,如果想在驗證用戶對象時一起驗證地址對象的話,在地址對象上加@Valid註解即可級聯驗證 |
此處只列出Hibernate Validator提供的大部分驗證約束註解,請參考hibernate validator官方文檔瞭解其他驗證約束註解和進行自定義的驗證約束註解定義。
具體演示實例請參考cn.javass.chapter7.web.controller.validate.ValidatorAnnotationTestController。
7.4.2.3、錯誤消息
當驗證出錯時,我們需要給用戶展示錯誤消息告訴用戶出錯的原因,因此我們要爲驗證約束註解指定錯誤消息。錯誤消息是通過在驗證約束註解的message屬性指定。驗證約束註解指定錯誤消息有如下兩種方式:
1、硬編碼錯誤消息;
2、從資源消息文件中根據消息鍵讀取錯誤消息。
一、硬編碼錯誤消息
直接在驗證約束註解上指定錯誤消息,如下所示:
- @NotNull(message = "用戶名不能爲空")
- @Length(min=5, max=20, message="用戶名長度必須在5-20之間")
- @Pattern(regexp = "^[a-zA-Z_]\\w{4,19}$", message = "用戶名必須以字母下劃線開頭,可由字母數字下劃線組成")
- private String username;
如上所示,錯誤消息使用硬編碼指定,這種方式是不推薦使用的,因爲在如下場景是不適用的:
1、在國際化場景下,需要對不同的國家顯示不同的錯誤消息;
2、需要更換錯誤消息時是比較麻煩的,需要找到相應的類進行更換,並重新編譯發佈。
二、從資源消息文件中根據消息鍵讀取錯誤消息
2.1、默認的錯誤消息文件及默認錯誤消息鍵值
默認的錯誤消息文件是/org/hibernate/validator/ValidationMessages.properties,如下圖所示:
默認的錯誤消息鍵值如下圖所示:
消息鍵默認爲:驗證約束註解的全限定類名.message
在我們之前的測試文件中,錯誤消息鍵值是使用默認的,如何自定義錯誤消息文件和錯誤消息鍵值呢?
2.2、自定義的錯誤消息文件和錯誤消息鍵值
自定義的錯誤消息文件裏的錯誤消息鍵值將覆蓋默認的錯誤消息文件中的錯誤消息鍵值。我們自定義的錯誤消息文件是具有國際化功能的。
(1、定義錯誤消息文件
在類裝載路徑的根下創建ValidationMessages.properties文件,如在src目錄下創建會自動複製到類裝載路徑的根下,並添加如下消息鍵值(需要native2ascii,可以在eclipse裏裝Properties Editor,自動保存爲ASCII碼):
需要在你的spring配置文件WEB-INF/chapter7-servlet.xml修改之前的validator Bean:
- <bean id="validator"
- class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
- <property name="providerClass"
- value="org.hibernate.validator.HibernateValidator"/>
- </bean>
此時錯誤消息鍵值的查找會先到classpath下ValidationMessages.properties中找,找不到再到默認的錯誤消息文件中找。
輸入測試地址:http://localhost:9080/springmvc-chapter7/validate/pattern?value=zhan,將看到我們自定義的錯誤消息顯示出來了。
(2、使用Spring的MessageSource Bean進行消息鍵值的查找
如果我們的環境是與spring集成,還是應該使用Spring提供的消息支持,具體配置如下:
在spring配置文件WEB-INF/chapter7-servlet.xml定義MessageSource Bean:
- <bean id="messageSource"
- class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
- <property name="basename" value="classpath:messages"/>
- <property name="fileEncodings" value="utf-8"/>
- <property name="cacheSeconds" value="120"/>
- </bean>
之前我們已經配置過了,在此就不詳述了。
在spring配置文件WEB-INF/chapter7-servlet.xml定義的validator Bean,添加如下屬性:
驗證失敗的錯誤消息鍵值的查找將使用messageSource Bean進行。
在消息文件src/messages.properties中添加如下錯誤消息:
輸入測試地址:http://localhost:9080/springmvc-chapter7/validate/pattern?value=zhan,將看到我們自定義的錯誤消息顯示出來了。
當我們配置了messageSource Bean時,默認將爲驗證的對象自動生成如下錯誤消息鍵:
驗證錯誤註解簡單類名.驗證對象名.字段名
驗證錯誤註解簡單類名.字段名
驗證錯誤註解簡單類名.字段類型全限定類名
驗證錯誤註解簡單類名
使用的優先級是:從高到低,即最前邊的具有最高的優先級,而且以上所有默認的錯誤消息鍵優先級高於自定義的錯誤消息鍵。
如測試用例cn.javass.chapter7.web.controller.validate.ValidatorAnnotationTestController中的public String pattern(@Valid @ModelAttribute("model") PatternModel model, Errors errors)將自動產生如下錯誤消息鍵:
Pattern.model.value=驗證錯誤註解簡單類名.驗證對象名.字段名
Pattern.value=驗證錯誤註解簡單類名.字段名
Pattern.java.lang.String=驗證錯誤註解簡單類名.字段類型全限定類名
Pattern=驗證錯誤註解簡單類名
(3、自定義錯誤消息鍵值
之前我們已經學習了硬編碼錯誤消息,及默認的錯誤消息,在大部分場景下,以上兩種方式無法滿足我們的需求,因此我們需要自定義錯誤消息鍵值。
在驗證約束註解上指定錯誤消息鍵:
- package cn.javass.chapter7.web.controller.validate.model;
- public class PatternModel {
- @Pattern(regexp = "^[a-zA-Z_][\\w]{4,19}$", message="{user.name.error}")
- private String value;
- }
我們可以通過驗證約束註解的message屬性指定錯誤消息鍵,格式如“{消息鍵}”。
在消息文件src/messages.properties中添加如下錯誤消息:
輸入測試地址:http://localhost:9080/springmvc-chapter7/validate/pattern?value=zhan,將看到我們自定義的錯誤消息顯示出來了。
接下來我們看下如下場景
錯誤消息中的5-20應該是從@Length驗證約束註解中獲取的,而不是在錯誤消息中硬編碼,因此我們需要佔位符的支持:
●如@Length(min=5, max=20, message="{user.name.length.error}"),錯誤消息可以這樣寫:用戶名長度必須在{min}-{max}之間
錯誤消息佔位符規則:
{驗證註解屬性名},如@Length有min和max屬性,則在錯誤消息文件中可以通過{min}和{max}來獲取;如@Max有value屬性,則在錯誤消息文件中可以通過{value}來獲取。
輸入測試地址:http://localhost:9080/springmvc-chapter7/validate/length?value=1,將看到我們自定義的錯誤消息顯示出來了。
7.4.2.4、功能處理方法上多個驗證參數的處理
當我們在一個功能處理方法上需要驗證多個模型對象時,需要通過如下形式來獲取驗證結果:
- @RequestMapping("/validate/multi")
- public String multi(
- @Valid @ModelAttribute("a") A a, BindingResult aErrors,
- @Valid @ModelAttribute("b") B b, BindingResult bErrors) {
- if(aErrors.hasErrors()) { //如果a模型對象驗證失敗
- return "validate/error";
- }
- if(bErrors.hasErrors()) { //如果a模型對象驗證失敗
- return "validate/error";
- }
- return "redirect:/success";
- }
每一個模型對象後邊都需要跟一個Errors或BindingResult對象來保存驗證結果,其方法體內部可以使用這兩個驗證結果對象來選擇出錯時跳轉的頁面。詳見cn.javass.chapter7.web.controller.validate.MultiModelController。
在錯誤頁面,需要針對不同的模型來顯示錯誤消息:
- <form:form commandName="a">
- <form:errors path="*" cssStyle="color:red"></form:errors><br/>
- </form:form>
- <form:form commandName="b">
- <form:errors path="*" cssStyle="color:red"></form:errors><br/>
- </form:form>