1. 程式人生 > >SpringMVC資料驗證——註解式控制器的資料驗證、型別轉換及格式化

SpringMVC資料驗證——註解式控制器的資料驗證、型別轉換及格式化

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、控制器實現

Java程式碼  收藏程式碼
  1. @Controller  
  2. public class RegisterSimpleFormController {  
  3.     private UserModelValidator validator = new UserModelValidator();  
  4.     @ModelAttribute("user")        //① 暴露表單引用物件為模型資料  
  5.     public UserModel getUser() {  
  6.         return new UserModel();  
  7.     }  
  8.     @RequestMapping(value = "/validator", method = RequestMethod.GET)  
  9.     public String showRegisterForm() {   //② 表單展示  
  10.         return "validate/registerAndValidator";  
  11.     }  
  12.     @RequestMapping(value = "/validator", method = RequestMethod.POST)  
  13.     public String submitForm(  
  14.             @ModelAttribute("user") UserModel user,  
  15.             Errors errors) {           //③ 表單提交  
  16.         validator.validate(user, errors);  //1 呼叫UserModelValidator的validate方法進行驗證  
  17.         if(errors.hasErrors()) { //2如果有錯誤再回到表單展示頁面  
  18.             return showRegisterForm();  
  19.         }  
  20.         return "redirect:/success";  
  21.     }  
  22. }   

在submitForm方法中,我們首先呼叫之前寫的UserModelValidator的validate方法進行驗證,當然此處可以直接驗證並通過Errors介面來保留錯誤;此處還通過Errors介面的hasErrors方法來決定當驗證失敗時顯示的錯誤頁面。

(3、spring配置檔案chapter7-servlet.xml

Java程式碼  收藏程式碼
  1. <bean class="cn.javass.chapter7.web.controller.RegisterSimpleFormController"/>   

(4、錯誤碼配置(messages.properties),需要執行NativeToAscii

直接將【springmvc-chapter4】專案中src下的messages.properties複製到src目錄下。

在spring配置檔案chapter7-servlet.xml中新增messageSource:

Java程式碼  收藏程式碼
  1. <bean id="messageSource"   
  2. class="org.springframework.context.support.ReloadableResourceBundleMessageSource">  
  3.         <property name="basename" value="classpath:messages"/>  
  4.         <property name="fileEncodings" value="utf-8"/>  
  5.         <property name="cacheSeconds" value="120"/>  
  6. </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/lib/required/validation-api-1.0.0.GA.jar JSR-303規範API包
dist/hibernate-validator-4.3.0.Final.jar Hibernate 參考實現

(2、在Spring配置總新增對JSR-303驗證框架的支援   

Java程式碼  收藏程式碼
  1. <!-- 以下 validator  ConversionService 在使用 mvc:annotation-driven 會 自動註冊-->  
  2. <bean id="validator"   
  3. class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">  
  4.         <property name="providerClass"  value="org.hibernate.validator.HibernateValidator"/>  
  5.         <!-- 如果不加預設到 使用classpath下的 ValidationMessages.properties -->  
  6.         <property name="validationMessageSource" ref="messageSource"/>  
  7. </bean>   

此處使用Hibernatevalidator實現:

validationMessageSource屬性:指定國際化錯誤訊息從哪裡取,此處使用之前定義的messageSource來獲取國際化訊息;如果此處不指定該屬性,則預設到classpath下的ValidationMessages.properties取國際化錯誤訊息。

通過ConfigurableWebBindingInitializer註冊validator:

Java程式碼  收藏程式碼
  1. <bean id="webBindingInitializer"   
  2. class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">  
  3.         <property name="conversionService" ref="conversionService"/>  
  4.         <property name="validator" ref="validator"/>  
  5. </bean>   

其他配置和之前學習7.2.2.4一節一樣。

如上整合過程看起來比較麻煩,後邊我們會介紹<mvc:annotation-driven>和@EnableWebMvc,ConversionService會自動註冊,後續章節再詳細介紹。

(3、使用JSR-303驗證框架註解為模型物件指定驗證資訊

Java程式碼  收藏程式碼
  1. package cn.javass.chapter7.model;  
  2. import javax.validation.constraints.NotNull;  
  3. public class UserModel {  
  4.     @NotNull(message="{username.not.empty}")  
  5.     private String username;  
  6. }   

通過@NotNull指定此username欄位不允許為空,當驗證失敗時將從之前指定的messageSource中獲取“username.not.empty”對於的錯誤資訊,此處只有通過“{錯誤訊息鍵值}”格式指定的才能從messageSource獲取。

(4、控制器

Java程式碼  收藏程式碼
  1. package cn.javass.chapter7.web.controller.validate;  
  2. //省略import  
  3. @Controller  
  4. public class HelloWorldController {  
  5.     @RequestMapping("/validate/hello")  
  6.     public String validate(@Valid @ModelAttribute("user") UserModel user, Errors errors) {  
  7.         if(errors.hasErrors()) {  
  8.             return "validate/error";  
  9.         }  
  10.         return "redirect:/success";  
  11.     }  
  12. }   

通過在命令物件上註解@Valid來告訴SpringMVC此命令物件在繫結完畢後需要進行JSR-303驗證,如果驗證失敗會將錯誤資訊新增到errors錯誤物件中。

(5、驗證失敗後需要展示的頁面(/WEB-INF/jsp/validate/error.jsp)

Java程式碼  收藏程式碼
  1. <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>  
  2. <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>  
  3. <form:form commandName="user">  
  4.     <form:errors path="*" cssStyle="color:red"></form:errors><br/>  
  5. </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註解即可級聯驗證

此處只列出HibernateValidator提供的大部分驗證約束註解,請參考hibernate validator官方文件瞭解其他驗證約束註解和進行自定義的驗證約束註解定義。

具體演示例項請參考cn.javass.chapter7.web.controller.validate.ValidatorAnnotationTestController。

7.4.2.3、錯誤訊息

當驗證出錯時,我們需要給使用者展示錯誤訊息告訴使用者出錯的原因,因此我們要為驗證約束註解指定錯誤訊息。錯誤訊息是通過在驗證約束註解的message屬性指定。驗證約束註解指定錯誤訊息有如下兩種方式:

1、硬編碼錯誤訊息;

2、從資源訊息檔案中根據訊息鍵讀取錯誤訊息。

一、硬編碼錯誤訊息

直接在驗證約束註解上指定錯誤訊息,如下所示:

Java程式碼  收藏程式碼
  1. @NotNull(message = "使用者名稱不能為空")  
  2. @Length(min=5, max=20, message="使用者名稱長度必須在5-20之間")  
  3. @Pattern(regexp = "^[a-zA-Z_]\\w{4,19}$", message = "使用者名稱必須以字母下劃線開頭,可由字母數字下劃線組成")  
  4. private String username;   

如上所示,錯誤訊息使用硬編碼指定,這種方式是不推薦使用的,因為在如下場景是不適用的:

1、在國際化場景下,需要對不同的國家顯示不同的錯誤訊息;

2、需要更換錯誤訊息時是比較麻煩的,需要找到相應的類進行更換,並重新編譯釋出。

二、從資源訊息檔案中根據訊息鍵讀取錯誤訊息

2.1、預設的錯誤訊息檔案及預設錯誤訊息鍵值

預設的錯誤訊息檔案是/org/hibernate/validator/ValidationMessages.properties,如下圖所示:



 預設的錯誤訊息鍵值如下圖所示:



 訊息鍵預設為:驗證約束註解的全限定類名.message

在我們之前的測試檔案中,錯誤訊息鍵值是使用預設的,如何自定義錯誤訊息檔案和錯誤訊息鍵值呢?

2.2、自定義的錯誤訊息檔案和錯誤訊息鍵值

自定義的錯誤訊息檔案裡的錯誤訊息鍵值將覆蓋預設的錯誤訊息檔案中的錯誤訊息鍵值。我們自定義的錯誤訊息檔案是具有國際化功能的。

(1、定義錯誤訊息檔案

在類裝載路徑的根下建立ValidationMessages.properties檔案,如在src目錄下建立會自動複製到類裝載路徑的根下,並新增如下訊息鍵值(需要native2ascii,可以在eclipse裡裝Properties Editor,自動儲存為ASCII碼):

Java程式碼  收藏程式碼
  1. javax.validation.constraints.Pattern.message=使用者名稱必須以字母或下劃線開頭,後邊可以跟字母數字下劃線,長度在5-20之間  

需要在你的spring配置檔案WEB-INF/chapter7-servlet.xml修改之前的validatorBean:

Java程式碼  收藏程式碼
  1. <bean id="validator"   
  2. class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">  
  3.         <property name="providerClass"   
  4. value="org.hibernate.validator.HibernateValidator"/>  
  5. </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定義MessageSourceBean:

Java程式碼  收藏程式碼
  1. <bean id="messageSource"   
  2. class="org.springframework.context.support.ReloadableResourceBundleMessageSource">  
  3.         <property name="basename" value="classpath:messages"/>  
  4.         <property name="fileEncodings" value="utf-8"/>  
  5.         <property name="cacheSeconds" value="120"/>  
  6. </bean>   

之前我們已經配置過了,在此就不詳述了。

在spring配置檔案WEB-INF/chapter7-servlet.xml定義的validatorBean,新增如下屬性:

Java程式碼  收藏程式碼
  1. <property name="validationMessageSource" ref="messageSource"/>  

 驗證失敗的錯誤訊息鍵值的查詢將使用messageSource Bean進行。

在訊息檔案src/messages.properties中新增如下錯誤訊息:

Java程式碼  收藏程式碼
  1. javax.validation.constraints.Pattern.message=使用者名稱必須以字母或下劃線開頭,後邊可以跟字母數字下劃線,長度在5-20之間  

輸入測試地址:http://localhost:9080/springmvc-chapter7/validate/pattern?value=zhan,將看到我們自定義的錯誤訊息顯示出來了。

當我們配置了messageSource Bean時,預設將為驗證的物件自動生成如下錯誤訊息鍵:

驗證錯誤註解簡單類名.驗證物件名.欄位名

驗證錯誤註解簡單類名.欄位名

驗證錯誤註解簡單類名.欄位型別全限定類名

驗證錯誤註解簡單類名

使用的優先順序是:從高到低,即最前邊的具有最高的優先順序,而且以上所有預設的錯誤訊息鍵優先順序高於自定義的錯誤訊息鍵。

如測試用例cn.javass.chapter7.web.controller.validate.ValidatorAnnotationTestController中的publicString pattern(@Valid @ModelAttribute("model") PatternModel model,Errors errors)將自動產生如下錯誤訊息鍵:

Pattern.model.value=驗證錯誤註解簡單類名.驗證物件名.欄位名

Pattern.value=驗證錯誤註解簡單類名.欄位名

Pattern.java.lang.String=驗證錯誤註解簡單類名.欄位型別全限定類名

Pattern=驗證錯誤註解簡單類名

(3、自定義錯誤訊息鍵值

之前我們已經學習了硬編碼錯誤訊息,及預設的錯誤訊息,在大部分場景下,以上兩種方式無法滿足我們的需求,因此我們需要自定義錯誤訊息鍵值。

在驗證約束註解上指定錯誤訊息鍵:

Java程式碼  收藏程式碼
  1. package cn.javass.chapter7.web.controller.validate.model;  
  2. public class PatternModel {   
  3.     @Pattern(regexp = "^[a-zA-Z_][\\w]{4,19}$", message="{user.name.error}")  
  4.     private String value;  
  5. }   

我們可以通過驗證約束註解的message屬性指定錯誤訊息鍵,格式如“{訊息鍵}”。

在訊息檔案src/messages.properties中新增如下錯誤訊息:

Java程式碼  收藏程式碼
  1. user.name.error=使用者名稱格式不合法  

輸入測試地址:http://localhost:9080/springmvc-chapter7/validate/pattern?value=zhan,將看到我們自定義的錯誤訊息顯示出來了。

接下來我們看下如下場景

Java程式碼  收藏程式碼
  1. @Length(min=5, max=20, message="{user.name.length.error}")  
Java程式碼  收藏程式碼
  1. user.name.error=使用者名稱長度必須在5-20之間  

錯誤訊息中的5-20應該是從@Length驗證約束註解中獲取的,而不是在錯誤訊息中硬編碼,因此我們需要佔位符的支援:

●如@Length(min=5,max=20, message="{user.name.length.error}"),錯誤訊息可以這樣寫:使用者名稱長度必須在{min}-{max}之間

錯誤訊息佔位符規則:

{驗證註解屬性名},如@Length有min和max屬性,則在錯誤訊息檔案中可以通過{min}和{max}來獲取;如@Max有value屬性,則在錯誤訊息檔案中可以通過{value}來獲取。

Java程式碼  收藏程式碼
  1. user.name.length.error=使用者名稱長度必須在{min}-{max}之間  

輸入測試地址:http://localhost:9080/springmvc-chapter7/validate/length?value=1,將看到我們自定義的錯誤訊息顯示出來了。

7.4.2.4、功能處理方法上多個驗證引數的處理

當我們在一個功能處理方法上需要驗證多個模型物件時,需要通過如下形式來獲取驗證結果:

Java程式碼  收藏程式碼
  1. @RequestMapping("/validate/multi")  
  2. public String multi(  
  3.             @Valid @ModelAttribute("a") A a, BindingResult aErrors,  
  4.             @Valid @ModelAttribute("b") B b, BindingResult bErrors) {  
  5.         if(aErrors.hasErrors()) { //如果a模型物件驗證失敗  
  6.             return "validate/error";  
  7.         }  
  8.         if(bErrors.hasErrors()) { //如果a模型物件驗證失敗  
  9.             return "validate/error";  
  10.         }  
  11.         return "redirect:/success";  
  12. }   

每一個模型物件後邊都需要跟一個Errors或BindingResult物件來儲存驗證結果,其方法體內部可以使用這兩個驗證結果物件來選擇出錯時跳轉的頁面。詳見cn.javass.chapter7.web.controller.validate.MultiModelController。

在錯誤頁面,需要針對不同的模型來顯示錯誤訊息:

Java程式碼  收藏程式碼
  1. <form:form commandName="a">  
  2.     <form:errors path="*" cssStyle="color:red"></form:errors><br/>  
  3. </form:form>  
  4. <form:form commandName="b">  
  5.     <form:errors path="*" cssStyle="color:red"></form:errors><br/>  
  6. </form:form>