第四章 Controller接口控制器詳解(7)

4.16.2、數據驗證

1、數據綁定失敗:比如需要數字卻輸入了字母;

2、數據不合法:可以認爲是業務錯誤,通過自定義驗證器驗證,如用戶名長度必須在5-20之間,我們卻輸入了100個字符等;

3、錯誤對象:當我們數據綁定失敗或驗證失敗後,錯誤信息存放的對象,我們叫錯誤對象,在Spring Web MVC中Errors是具體的代表者;線程不安全對象;

4、錯誤消息:是硬編碼,還是可配置?實際工作應該使用配置方式,我們只是把錯誤碼(errorCode)放入錯誤對象,在展示時讀取相應的錯誤消息配置文件來獲取要顯示的錯誤消息(errorMessage);

 

4.16.2.1、驗證流程

 

1、首先進行數據綁定驗證,如果驗證失敗會通過MessageCodesResolver生成錯誤碼放入Errors錯誤對象;

2、數據不合法驗證,通過自定義的驗證器驗證,如果失敗需要手動將錯誤碼放入Errors錯誤對象;

4.16.2.2、錯誤對象和錯誤消息

錯誤對象的代表者是Errors接口,並且提供了幾個實現者,在Spring Web MVC中我們使用的是如下實現:

 

相關的錯誤方法如下:

Errors:存儲和暴露關於數據綁定錯誤和驗證錯誤相關信息的接口,提供了相關存儲和獲取錯誤消息的方法:

 

java代碼:
  1. package org.springframework.validation;  
  2. public interface Errors {  
  3.   //=========================全局錯誤消息(驗證/綁定對象全局的)=============================  
  4.   //註冊一個全局的錯誤碼()  
  5.   void reject(String errorCode);  
  6.   //註冊一個全局的錯誤碼,當根據errorCode沒有找到相應錯誤消息時,使用defaultMessage作爲錯誤消息  
  7.   void reject(String errorCode, String defaultMessage);  
  8.   //註冊一個全局的錯誤碼,當根據errorCode沒有找到相應錯誤消息時(帶錯誤參數的),使用defaultMessage作爲錯誤消息  
  9.   void reject(String errorCode, Object[] errorArgs, String defaultMessage);  
  10.   //=========================全局錯誤消息(驗證/綁定整個對象的)=============================  
  11.   //=========================局部錯誤消息(驗證/綁定對象字段的)=============================  
  12.   //註冊一個對象字段的錯誤碼,field指定驗證失敗的字段名  
  13.   void rejectValue(String field, String errorCode);  
  14.   void rejectValue(String field, String errorCode, String defaultMessage);  
  15.   void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage);  
  16.   //=========================局部錯誤消息(驗證/綁定對象字段的)=============================  
  17.   boolean hasErrors();      ////是否有錯誤  
  18.   boolean hasGlobalErrors(); //是否有全局錯誤  
  19.   boolean hasFieldErrors();  //是否有字段錯誤  
  20.   Object getFieldValue(String field); //返回當前驗證通過的值,或驗證失敗時失敗的值;  
  21. }  

 

getFieldValue:可以得到驗證失敗的失敗值,這是其他Web層框架很少支持的,這樣就可以給用戶展示出錯時的值(而不是空或其他的默認值等)。

 

BindingResult:代表數據綁定的結果,繼承了Errors接口。

BindException:代表數據綁定的異常,它繼承Exception,並實現了BindingResult,這是內部使用的錯誤對象。

 

示例:

(1、控制器

 

java代碼:
  1. package cn.javass.chapter4.web.controller;  
  2. //省略import  
  3. public class ErrorController extends AbstractCommandController {  
  4.        public ErrorController() {  
  5.               setCommandClass(DataBinderTestModel.class);  
  6.               setCommandName("command");  
  7.        }  
  8.        @Override  
  9.        protected ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) throws Exception {     
  10.               //表示用戶名不爲空  
  11.               errors.reject("username.not.empty");  
  12.               //帶有默認錯誤消息  
  13.               errors.reject("username.not.empty1""用戶名不能爲空1");  
  14.               //帶有參數和默認錯誤消息          
  15.               errors.reject("username.length.error"new Object[]{510});  
  16.                
  17.               //得到錯誤相關的模型數據  
  18.               Map model = errors.getModel();  
  19.               return new ModelAndView("bindAndValidate/error", model);  
  20.        }  
  21. }  

 

errors.reject("username.not.empty"):註冊全局錯誤碼“username.not.empty”,我們必須提供messageSource來提供錯誤碼“username.not.empty”對應的錯誤信息(如果沒有會拋出NoSuchMessageException異常);

 

errors.reject("username.not.empty1", "用戶名不能爲空1"):註冊全局錯誤碼“username.not.empty1”,如果從messageSource沒沒有找到錯誤碼“username.not.empty1”對應的錯誤信息,則將顯示默認消息“用戶名不能爲空1”;

 

errors.reject("username.length.error", new Object[]{5, 10}):錯誤碼爲“username.length.error”,而且錯誤信息需要兩個參數,如我們在我們的配置文件中定義“用戶名長度不合法,長度必須在{0}到{1}之間”,則實際的錯誤消息爲“用戶名長度不合法,長度必須在5到10之間”

errors.getModel():當有錯誤信息時,一定將errors.getModel()放入我們要返回的ModelAndView中,以便使用裏邊的錯誤對象來顯示錯誤信息。

 

(2、spring配置文件chapter4-servlet.xml

 

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>  
  7.    
  8. <bean name="/error" class="cn.javass.chapter4.web.controller.ErrorController"/>  

 

messageSource:用於獲取錯誤碼對應的錯誤消息的,而且bean名字默認必須是messageSource。

 

messages.properties(需要執行NativeToAscii)

 

java代碼:
  1. username.not.empty=用戶名不能爲空  
  2. username.length.error=用戶名長度不合法,長度必須在{0}到{1}之間  

 

(3、視圖頁面(WEB-INF/jsp/bindAndValidate/error.jsp)

 

java代碼:
  1. <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %>  
  2. <!-- 表單的默認命令對象名爲command -->  
  3. <form:form commandName="command">  
  4.     <form:errors path="*"></form:errors>  
  5. </form:form>  

 

form標籤庫:此處我們使用了spring的form標籤庫;

<form:form commandName="command">:表示我們的表單標籤,commandName表示綁定的命令對象名字,默認爲command;

<form:errors path="*"></form:errors>:表示顯示錯誤信息的標籤,如果path爲“*”表示顯示所有錯誤信息。

 

接下來我們來看一下 數據綁定失敗和數據不合法時,如何處理。

4.16.2.3、數據綁定失敗

如我們的DataBinderTestModel類:

bool:boolean類型,此時如果我們前臺傳入非兼容的數據,則會數據綁定失敗;

date:Date類型,此時如果我們前臺傳入非兼容的數據,同樣會數據綁定失敗;

phoneNumber:自定義的PhoneNumberModel類型,如果如果我們前臺傳入非兼容的數據,同樣會數據綁定失敗。

 

示例:

(1、控制器,DataBinderErrorTestController。

 

 

java代碼:
  1. package cn.javass.chapter4.web.controller;  
  2. //省略import  
  3. public class DataBinderErrorTestController extends SimpleFormController {  
  4.        public DataBinderErrorTestController() {  
  5.               setCommandClass(DataBinderTestModel.class);  
  6.               setCommandName("dataBinderTest");  
  7.        }  
  8.        @Override  
  9.        protected ModelAndView showForm(HttpServletRequest request, HttpServletResponse response, BindException errors) throws Exception {  
  10.               //如果表單提交有任何錯誤都會再回到表單展示頁面  
  11.               System.out.println(errors);  
  12.               return super.showForm(request, response, errors);  
  13.        }  
  14.        @Override  
  15.        protected void doSubmitAction(Object command) throws Exception {  
  16.               System.out.println(command); //表單提交成功(數據綁定成功)進行功能處理  
  17.     }  
  18.        @Override  
  19.        protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {  
  20.               super.initBinder(request, binder);  
  21.               //註冊自定義的屬性編輯器  
  22.               //1、日期  
  23.               DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  24.               CustomDateEditor dateEditor = new CustomDateEditor(df, true);  
  25.               //表示如果命令對象有Date類型的屬性,將使用該屬性編輯器進行類型轉換  
  26.               binder.registerCustomEditor(Date.class, dateEditor);  
  27.                
  28.               //自定義的電話號碼編輯器  
  29.               binder.registerCustomEditor(PhoneNumberModel.classnew PhoneNumberEditor());  
  30.        }  
  31. }  

此處我們使用SimpleFormController;

showForm:展示表單,當提交表單有任何數據綁定錯誤會再回到該方法進行表單輸入(在此處我們打印錯誤對象);

doSubmitAction:表單提交成功,只要當表單的數據到命令對象綁定成功時,纔會執行;

 

(2、spring配置文件chapter4-servlet.xml

 

java代碼:
  1. <bean name="/dataBindError"  
  2. class="cn.javass.chapter4.web.controller.DataBinderErrorTestController">  
  3.    <property name="formView" value="bindAndValidate/input"/>  
  4.    <property name="successView" value="bindAndValidate/success"/>  
  5. </bean>  

 

(3、視圖頁面(WEB-INF/jsp/bindAndValidate/ input.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. <!-- 表單的命令對象名爲dataBinderTest -->  
  4. <form:form commandName="dataBinderTest">  
  5.     <form:errors path="*" cssStyle="color:red"></form:errors><br/><br/>  
  6.     bool:<form:input path="bool"/><br/>  
  7.     phoneNumber:<form:input path="phoneNumber"/><br/>  
  8.     date:<form:input path="date"/><br/>  
  9.     <input type="submit" value="提交"/>  
  10. </form:form>  

 

此處一定要使用form標籤庫,藉此我們可以看到它的強大支持(別的大部分Web框架所不具備的,展示用戶驗證失敗的數據)。

 

<form:form commandName="dataBinderTest">:指定命令對象爲dataBinderTest,默認command;

<form:errors path="*" cssStyle="color:red"></form:errors>:顯示錯誤消息,當提交表單有錯誤時展示錯誤消息(數據綁定錯誤/數據不合法);

<form:input path="bool"/>:等價於(<input type=’text’>),但會從命令對象中取出bool屬性進行填充value屬性,或如果表單提交有錯誤會從錯誤對象取出之前的錯誤數據(而非空或默認值);

<input type="submit" value="提交"/>:spring沒有提供相應的提交按鈕,因此需要使用html的。

 

(4、測試

在地址欄輸入如下地址:http://localhost:9080/springmvc-chapter4/dataBindError

 

全部是錯誤數據,即不能綁定到我們的命令對象;

 

當提交表單後,我們又回到表單輸入頁面,而且輸出了一堆錯誤信息

 

1、錯誤消息不可讀;

2、表單元素可以顯示之前的錯誤的數據,而不是默認值/空;

 

(5、問題

這裏最大的問題是不可讀的錯誤消息,如何讓這些錯誤消息可讀呢?

 

首先我們看我們的showForm方法裏輸出的“errors”錯誤對象信息:

 

java代碼:
  1. org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors  
  2.    
  3. Field error in object 'dataBinderTest' on field 'bool': rejected value [www]; codes [typeMismatch.dataBinderTest.bool,typeMismatch.bool,typeMismatch.boolean,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dataBinderTest.bool,bool]; arguments []; default message [bool]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'boolean' for property 'bool'; nested exception is java.lang.IllegalArgumentException: Invalid boolean value [www]]  
  4.    
  5. Field error in object 'dataBinderTest' on field 'date': rejected value [123]; codes [typeMismatch.dataBinderTest.date,typeMismatch.date,typeMismatch.java.util.Date,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dataBinderTest.date,date]; arguments []; default message [date]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'date'; nested exception is java.lang.IllegalArgumentException: Could not parse date: Unparseable date: "123"]  
  6.    
  7. Field error in object 'dataBinderTest' on field 'phoneNumber': rejected value [123]; codes [typeMismatch.dataBinderTest.phoneNumber,typeMismatch.phoneNumber,typeMismatch.cn.javass.chapter4.model.PhoneNumberModel,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dataBinderTest.phoneNumber,phoneNumber]; arguments []; default message [phoneNumber]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'cn.javass.chapter4.model.PhoneNumberModel' for property 'phoneNumber'; nested exception is java.lang.IllegalArgumentException: 類型轉換失敗,需要格式[010-12345678],但格式是[123]]  
  8.    

數據綁定失敗(類型不匹配)會自動生成如下錯誤碼(錯誤碼對應的錯誤消息按照如下順序依次查找):

1、typeMismatch.命令對象名.屬性名

2、typeMismatch.屬性名

3、typeMismatch.屬性全限定類名(包名.類名)

4、typeMismatch

 

⊙內部使用MessageCodesResolver解析數據綁定錯誤到錯誤碼,默認DefaultMessageCodesResolver,因此想要詳細瞭解如何解析請看其javadoc;

 

⊙建議使用第1個進行錯誤碼的配置。

 

因此修改我們的messages.properties添加如下錯誤消息(需要執行NativeToAscii):

 

java代碼:
  1. typeMismatch.dataBinderTest.date=您輸入的數據格式錯誤,請重新輸入(格式:2012-03-19 22:17:17)  
  2. #typeMismatch.date=2  
  3. #typeMismatch.java.util.Date=3  
  4. #typeMismatch=4  

 

再次提交表單我們會看到我們設置的錯誤消息:

 

到此,數據綁定錯誤我們介紹完了,接下來我們再看一下數據不合法錯誤。

4.16.2.4、數據不合法

1、比如用戶名長度必須在5-20之間,而且必須以字母開頭,可包含字母、數字、下劃線;

2、比如註冊用戶時 用戶名已經存在或郵箱已經存在等;

3、比如去一些論壇經常會發現,您發的帖子中包含×××屏蔽關鍵字等。

 

還有很多數據不合法的場景,在此就不羅列了,對於數據不合法,Spring Web MVC提供了兩種驗證方式:

◆編程式驗證器驗證

◆聲明式驗證

 

先從編程式驗證器開始吧。

4.16.2.4.1、編程式驗證器

一、驗證器接口

 

java代碼:
  1. package org.springframework.validation;  
  2. public interface Validator {  
  3. boolean supports(Class<?> clazz);  
  4. void validate(Object target, Errors errors);  
  5. }  

 

Validator接口:驗證器,編程實現數據驗證的接口;

supports方法:當前驗證器是否支持指定的clazz驗證,如果支持返回true即可;

validate方法:驗證的具體方法,target參數表示要驗證的目標對象(如命令對象),errors表示驗證出錯後存放錯誤信息的錯誤對象。

 

示例:

(1、驗證器實現

 

java代碼:
  1. package cn.javass.chapter4.web.controller.support.validator;  
  2. //省略import  
  3. public class UserModelValidator implements Validator {  
  4.     private static final Pattern USERNAME_PATTERN = Pattern.compile("[a-zA-Z]\\w{4,19}");  
  5.     private static final Pattern PASSWORD_PATTERN = Pattern.compile("[a-zA-Z0-9]{5,20}");  
  6.     private static final Set<String> FORBINDDDEN_WORD_SET = new HashSet<String>();  
  7.     static {  
  8.        FORBINDDDEN_WORD_SET.add("fuc k"); //刪掉空格  
  9.        FORBINDDDEN_WORD_SET.add("admin");  
  10.     }    
  11.     @Override  
  12.     public boolean supports(Class<?> clazz) {  
  13.        return UserModel.class == clazz;//表示只對UserModel類型的目標對象實施驗證  
  14.     }  
  15.     @Override  
  16.     public void validate(Object target, Errors errors) {  
  17.        //這個表示如果目標對象的username屬性爲空,則表示錯誤(簡化我們手工判斷是否爲空)  
  18.        ValidationUtils.rejectIfEmpty(errors, "username""username.not.empty");  
  19.         
  20.        UserModel user = (UserModel) target;  
  21.         
  22.        if(!USERNAME_PATTERN.matcher(user.getUsername()).matches()) {  
  23.            errors.rejectValue("username""username.not.illegal");//如果用戶名不合法  
  24.        }  
  25.         
  26.        for(String forbiddenWord : FORBINDDDEN_WORD_SET) {  
  27.            if(user.getUsername().contains(forbiddenWord)) {  
  28.               errors.rejectValue("username""username.forbidden"new Object[]{forbiddenWord}, "您的用戶名包含非法關鍵詞");//用戶名包含屏蔽關鍵字  
  29.               break;  
  30.            }  
  31.        }  
  32.        if(!PASSWORD_PATTERN.matcher(user.getPassword()).matches()) {  
  33.            errors.rejectValue("password","password.not.illegal""密碼不合法");//密碼不合法  
  34.        }    
  35.     }  
  36. }  
  37.    

supports方法:表示只對UserModel類型的對象驗證;

validate方法:數據驗證的具體方法,有如下幾個驗證:

    1、用戶名不合法(長度5-20,以字母開頭,隨後可以是字母、數字、下劃線)

    USERNAME_PATTERN.matcher(user.getUsername()).matches() //使用正則表達式驗證

    errors.rejectValue("username", "username.not.illegal");//驗證失敗爲username字段添加錯誤碼

 

 

    2、屏蔽關鍵詞:即用戶名中含有不合法的數據(如admin)

    user.getUsername().contains(forbiddenWord) //用contains來判斷我們的用戶名中是否含有非法關鍵詞

    errors.rejectValue("username", "username.forbidden", new Object[]{forbiddenWord}, "您的用戶名包含非法關鍵詞");//驗證失敗爲username字段添加錯誤碼(參數爲當前屏蔽關鍵詞)(默認消息爲"您的用戶名包含非法關鍵詞")

 

    3、密碼不合法

    在此就不羅列代碼了;

    4、ValidationUtils

    ValidationUtils.rejectIfEmpty(errors, "username", "username.not.empty");

    表示如果目標對象的username屬性數據爲空,則添加它的錯誤碼;

    內部通過(value == null || !StringUtils.hasLength(value.toString()))實現判斷value是否爲空,從而簡化代碼。

 

(2、spring配置文件chapter4-servlet.xml

 

java代碼:
  1. <bean id="userModelValidator"  
  2. class="cn.javass.chapter4.web.controller.support.validator.UserModelValidator"/>  
  3. <bean name="/validator"  
  4. class="cn.javass.chapter4.web.controller.RegisterSimpleFormController">  
  5.     <property name="formView" value="registerAndValidator"/>  
  6.     <property name="successView" value="redirect:/success"/>  
  7.     <property name="validator" ref="userModelValidator"/>  
  8. </bean>  

 

此處使用了我們第4.9節創建的RegisterSimpleFormController。

 

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

 

java代碼:
  1. username.not.empty=用戶名不能爲空  
  2. username.not.illegal=用戶名錯誤,必須以字母開頭,只能出現字母、數字、下劃線,並且長度在5-20之間  
  3. username.forbidden=用戶名中包含非法關鍵詞【{0}】  
  4. password.not.illegal=密碼長度必須在5-20之間  

 

(4、視圖頁面(/WEB-INF/jsp/registerAndValidator.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.    
  5. <form:errors path="*" cssStyle="color:red"></form:errors><br/>  
  6.    
  7. username:<form:input path="username"/>  
  8. <form:errors path="username" cssStyle="color:red"></form:errors>  
  9. <br/>  
  10.    
  11. password:<form:password path="password"/>  
  12. <form:errors path="password" cssStyle="color:red"></form:errors>  
  13. <br/>  
  14. <input type="submit" value="註冊"/>  
  15. </form:form>  
  16.    

 

form:errors path="username":表示只顯示username字段的錯誤信息;

 

(5、測試

地址:http://localhost:9080/springmvc-chapter4/validator

 

當我們輸入錯誤的數據後,會報錯(form:errors path="*"顯示所有錯誤信息,而form:errors path="username"只顯示該字段相關的)。

 

 

問題:

如MultiActionController控制器相關方法沒有提供給我們errors對象(Errors),我們應該怎麼進行錯誤處理呢?

 

此處給大家一個思路,errors本質就是一個Errors接口實現,而且在頁面要讀取相關的錯誤對象,該錯誤對象應該存放在模型對象裏邊,因此我們可以自己創建個errors對象並將其添加到模型對象中即可。

 

此處我們複製4.15節的UserController類爲UserAndValidatorController,並修改它的create(新增)方法添加如下代碼片段:

 

java代碼:
  1. BindException errors = new BindException(user, getCommandName(user));  
  2. //如果用戶名爲空  
  3. if(!StringUtils.hasLength(user.getUsername())) {  
  4.     errors.rejectValue("username""username.not.empty");  
  5. }  
  6. if(errors.hasErrors()) {  
  7.     return new ModelAndView(getCreateView()).addAllObjects(errors.getModel());  
  8. }  
  9.         

 

√ new BindException(user, getCommandName(user)):使用當前的命令對象,和命令對象的名字創建了一個BindException作爲errors;

√StringUtils.hasLength(user.getUsername()):如果用戶名爲空就是用errors.rejectValue("username", "username.not.empty");注入錯誤碼;

√errors.hasErrors():表示如果有錯誤就返回到新增頁面並顯示錯誤消息;

√ModelAndView(getCreateView()).addAllObjects(errors.getModel()):此處一定把errors對象的模型數據放在當前的ModelAndView中,作爲當前請求的模型數據返回。

 

在瀏覽器地址欄輸入:http://localhost:9080/springmvc-chapter4/userAndValidator/create 到新增頁面

 

用戶名什麼都不輸入,提交後又返回到新增頁面 而且顯示了錯誤消息說明我們的想法是正確的。

 

4.16.2.4.2、聲明式驗證器

從Spring3開始支持JSR-303驗證框架,支持XML風格和註解風格的驗證,目前在@RequestMapping時才能使用,也就是說基於Controller接口的實現不能使用該方式(但可以使用編程式驗證,有需要的可以參考hibernate validator實現),我們將在第七章詳細介紹。

 

 

到此Spring2風格的控制器我們就介紹完了,以上控制器從spring3.0開始已經不推薦使用了(但考慮到還有部分公司使用這些@Deprecated類,在此也介紹了一下),而是使用註解控制器實現(@Controller和@RequestMapping)。

 


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