1. 程式人生 > >第四章 Controller介面控制器詳解(5)

第四章 Controller介面控制器詳解(5)

4.16、資料型別轉換和資料驗證

流程:

1、首先建立資料繫結器,在此此會建立ServletRequestDataBinder類的物件,並設定messageCodesResolver(錯誤碼解析器);

2、提供第一個擴充套件點,初始化資料繫結器,在此處我們可以覆蓋該方法註冊自定義的PropertyEditor(請求引數——>命令物件屬性的轉換);

3、進行資料繫結,即請求引數——>命令物件的繫結;

4、提供第二個擴充套件點,資料繫結完成後的擴充套件點,此處可以實現一些自定義的繫結動作;

5、驗證器物件的驗證,驗證器通過validators注入,如果驗證失敗,需要把錯誤資訊放入Errors(此處使用BindException實現);

6、提供第三個擴充套件點,此處可以實現自定義的繫結/驗證邏輯;

7、將errors傳入功能處理方法進行處理,功能方法應該判斷該錯誤物件是否有錯誤進行相應的處理。

 

4.16.1、資料型別轉換

請求引數(String)——>命令物件屬性(可能是任意型別)的型別轉換,即資料繫結時的型別轉換,使用PropertyEditor實現繫結時的型別轉換。

 

一、Spring內建的PropertyEditor如下所示:

類名

說明

預設是否註冊

ByteArrayPropertyEditor

String<——>byte[]

ClassEditor

String<——>Class

當類沒有發現丟擲IllegalArgumentException

CustomBooleanEditor

String<——>Boolean

true/yes/on/1轉換為true,false/no/off/0轉換為false

CustomCollectionEditor

陣列/Collection——>Collection

普通值——>Collection(只包含一個物件)

如String——>Collection

不允許Collection——>String(單方向轉換)

CustomNumberEditor

String<——>Number(Integer、Long、Double)

FileEditor

String<——>File

InputStreamEditor

String——>InputStream

單向的,不能InputStream——>String

LocaleEditor

String<——>Locale,

(String的形式為[語言]_[國家]_[變數],這與Local物件的toString()方法得到的結果相同)

PatternEditor

String<——>Pattern

PropertiesEditor

String<——>java.lang.Properties

URLEditor

String<——>URL

StringTrimmerEditor

一個用於trim 的 String型別的屬性編輯器

如預設刪除兩邊的空格,charsToDelete屬性:可以設定為其他字元

emptyAsNull屬性:將一個空字串轉化為null值的選項。

×

CustomDateEditor

String<——>java.util.Date

×

 

二、Spring內建的PropertyEditor支援的屬性(符合JavaBean規範)操作:

表示式

設值/取值說明

username

屬性username

設值方法setUsername()/取值方法getUsername() 或 isUsername()

schooInfo.schoolType

屬性schooInfo的巢狀屬性schoolType

設值方法getSchooInfo().setSchoolType()/取值方法getSchooInfo().getSchoolType()

hobbyList[0]

屬性hobbyList的第一個元素

索引屬性可能是一個數組、列表、其它天然有序的容器。

map[key]

屬性map(java.util.Map型別)

map中key對應的值

 

三、示例:

接下來我們寫自定義的屬性編輯器進行資料繫結:

(1、模型物件:


java程式碼: Java程式碼   收藏程式碼
  1. package cn.javass.chapter4.model;  
  2. //省略import  
  3. public class DataBinderTestModel {  
  4.     private String username;  
  5.     private boolean bool;//Boolean值測試  
  6.     private SchoolInfoModel schooInfo;  
  7.     private List hobbyList;//集合測試,此處可以改為陣列/Set進行測試  
  8.     private Map map;//Map測試  
  9.     private PhoneNumberModel phoneNumber;//String->自定義物件的轉換測試  
  10.     private Date date;//日期型別測試  
  11.     private UserState state;//String——>Enum型別轉換測試  
  12.     //省略getter/setter  
  13. }  
  14.   
  15. package cn.javass.chapter4.model;  
  16. //如格式010-12345678  
  17. public class PhoneNumberModel {  
  18.     private String areaCode;//區號  
  19.     private String phoneNumber;//電話號碼  
  20.     //省略getter/setter  
  21. }  

(2、PhoneNumber屬性編輯器

前臺輸入如010-12345678自動轉換為PhoneNumberModel。

 

java程式碼: Java程式碼   收藏程式碼
  1. package cn.javass.chapter4.web.controller.support.editor;  
  2. //省略import  
  3. public class PhoneNumberEditor extends PropertyEditorSupport {  
  4.     Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$");  
  5.     @Override  
  6.     public void setAsText(String text) throws IllegalArgumentException {  
  7.         if(text == null || !StringUtils.hasLength(text)) {  
  8.             setValue(null); //如果沒值,設值為null  
  9.         }  
  10.         Matcher matcher = pattern.matcher(text);  
  11.         if(matcher.matches()) {  
  12.             PhoneNumberModel phoneNumber = new PhoneNumberModel();  
  13.             phoneNumber.setAreaCode(matcher.group(1));  
  14.             phoneNumber.setPhoneNumber(matcher.group(2));  
  15.             setValue(phoneNumber);  
  16.         } else {  
  17.             throw new IllegalArgumentException(String.format("型別轉換失敗,需要格式[010-12345678],但格式是[%s]", text));  
  18.         }  
  19.     }  
  20.     @Override  
  21.     public String getAsText() {  
  22.         PhoneNumberModel phoneNumber = ((PhoneNumberModel)getValue());  
  23.         return phoneNumber == null ? "" : phoneNumber.getAreaCode() + "-" + phoneNumber.getPhoneNumber();  
  24.     }  
  25. }  

PropertyEditorSupport:一個PropertyEditor的支援類;

setAsText:表示將String——>PhoneNumberModel,根據正則表示式進行轉換,如果轉換失敗丟擲異常,則接下來的驗證器會進行驗證處理;

getAsText:表示將PhoneNumberModel——>String。

 

(3、控制器

需要在控制器註冊我們自定義的屬性編輯器。

此處我們使用AbstractCommandController,因為它繼承了BaseCommandController,擁有繫結流程。

 

java程式碼: Java程式碼   收藏程式碼
  1. package cn.javass.chapter4.web.controller;  
  2. //省略import  
  3. public class DataBinderTestController extends AbstractCommandController {  
  4.     public DataBinderTestController() {  
  5.         setCommandClass(DataBinderTestModel.class); //設定命令物件  
  6.         setCommandName("dataBinderTest");//設定命令物件的名字  
  7.     }  
  8.     @Override  
  9.     protected ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) throws Exception {  
  10.         //輸出command物件看看是否繫結正確  
  11.         System.out.println(command);  
  12.         return new ModelAndView("bindAndValidate/success").addObject("dataBinderTest", command);  
  13.     }  
  14.     @Override  
  15.     protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {  
  16.         super.initBinder(request, binder);  
  17.         //註冊自定義的屬性編輯器  
  18.         //1、日期  
  19.         DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  20.         CustomDateEditor dateEditor = new CustomDateEditor(df, true);  
  21.         //表示如果命令物件有Date型別的屬性,將使用該屬性編輯器進行型別轉換  
  22.         binder.registerCustomEditor(Date.class, dateEditor);  
  23.         //自定義的電話號碼編輯器  
  24.         binder.registerCustomEditor(PhoneNumberModel.classnew PhoneNumberEditor());  
  25.     }  
  26. }  

initBinder:第一個擴充套件點,初始化資料繫結器,在此處我們註冊了兩個屬性編輯器;

CustomDateEditor:自定義的日期編輯器,用於在String<——>日期之間轉換;

    binder.registerCustomEditor(Date.class, dateEditor):表示如果命令物件是Date型別,則使用dateEditor進行型別轉換;

PhoneNumberEditor:自定義的電話號碼屬性編輯器用於在String<——> PhoneNumberModel之間轉換;

    binder.registerCustomEditor(PhoneNumberModel.classnewPhoneNumberEditor()):表示如果命令物件是PhoneNumberModel型別,則使用PhoneNumberEditor進行型別轉換;

(4、spring配置檔案chapter4-servlet.xml


java程式碼: Java程式碼   收藏程式碼
  1. <bean name="/dataBind"   
  2. class="cn.javass.chapter4.web.controller.DataBinderTestController"/>  

(5、檢視頁面(WEB-INF/jsp/bindAndValidate/success.jsp)


java程式碼: Java程式碼   收藏程式碼
  1. EL phoneNumber:${dataBinderTest.phoneNumber}<br/>  
  2. EL state:${dataBinderTest.state}<br/>  
  3. EL date:${dataBinderTest.date}<br/>  

檢視頁面的資料沒有預期被格式化,如何進行格式化顯示呢?請參考【第七章  註解式控制器的資料驗證、型別轉換及格式化】。

 

(6、測試:

1、在瀏覽器位址列輸入請求的URL,如

http://localhost:9080/springmvc-chapter4/dataBind?username=zhang&bool=yes&schooInfo.specialty=computer&hobbyList[0]=program&hobbyList[1]=music&map[key1]=value1&map[key2]=value2&phoneNumber=010-12345678&date=2012-3-18 16:48:48&state=blocked

 

2、控制器輸出的內容:

DataBinderTestModel [username=zhang, bool=true, schooInfo=SchoolInfoModel [schoolType=null, schoolName=null, specialty=computer], hobbyList=[program, music], map={key1=value1, key2=value2}, phoneNumber=PhoneNumberModel [areaCode=010, phoneNumber=12345678], date=Sun Mar 18 16:48:48 CST 2012, state=鎖定]

 

型別轉換如圖所示:

 

四、註冊PropertyEditor

1、使用WebDataBinder進行控制器級別註冊PropertyEditor(控制器獨享)

如“【三、示例】”中所使用的方式,使用WebDataBinder註冊控制器級別的PropertyEditor,這種方式註冊的PropertyEditor只對當前控制器獨享,即其他的控制器不會自動註冊這個PropertyEditor,如果需要還需要再註冊一下。

 

2、使用WebBindingInitializer批量註冊PropertyEditor

如果想在多個控制器同時註冊多個相同的PropertyEditor時,可以考慮使用WebBindingInitializer。

 

示例:

(1、實現WebBindingInitializer


java程式碼: Java程式碼   收藏程式碼
  1. package cn.javass.chapter4.web.controller.support.initializer;  
  2. //省略import  
  3. public class MyWebBindingInitializer implements WebBindingInitializer {  
  4.     @Override  
  5.     public void initBinder(WebDataBinder binder, WebRequest request) {  
  6.         //註冊自定義的屬性編輯器  
  7.         //1、日期  
  8.         DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  9.         CustomDateEditor dateEditor = new CustomDateEditor(df, true);  
  10.         //表示如果命令物件有Date型別的屬性,將使用該屬性編輯器進行型別轉換  
  11.         binder.registerCustomEditor(Date.class, dateEditor);  
  12.         //自定義的電話號碼編輯器  
  13.         binder.registerCustomEditor(PhoneNumberModel.classnew PhoneNumberEditor());  
  14.     }  
  15. }  

通過實現WebBindingInitializer並通過binder註冊多個PropertyEditor。

 

(2、修改【三、示例】中的DataBinderTestController,註釋掉initBinder方法;

 

(3、修改chapter4-servlet.xml配置檔案:

 

java程式碼: Java程式碼   收藏程式碼
  1. <!-- 註冊WebBindingInitializer實現 -->  
  2. <bean id="myWebBindingInitializer" class="cn.javass.chapter4.web.controller.support.initializer.MyWebBindingInitializer"/>  
  3. <bean name="/dataBind" class="cn.javass.chapter4.web.controller.DataBinderTestController">  
  4.     <!-- 注入WebBindingInitializer實現 -->  
  5.     <property name="webBindingInitializer" ref="myWebBindingInitializer"/>  
  6. </bean>  

(4、嘗試訪問“【三、示例】”中的測試URL即可成功。

 

使用WebBindingInitializer的好處是當你需要在多個控制器中需要同時使用多個相同的PropertyEditor可以在WebBindingInitializer實現中註冊,這樣只需要在控制器中注入WebBindingInitializer即可注入多個PropertyEditor。

 

3、全域性級別註冊PropertyEditor(全域性共享)

只需要將我們自定義的PropertyEditor放在和你的模型類同包下即可,且你的Editor命名規則必須是“模型類名Editor”,這樣Spring會自動使用標準JavaBean架構進行自動識別,如圖所示:

此時我們把“DataBinderTestController”的“binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());”註釋掉,再嘗試訪問“【三、示例】”中的測試URL即可成功。

 

這種方式不僅僅在使用Spring時可用,在標準的JavaBean等環境都是可用的,可以認為是全域性共享的(不僅僅是Spring環境)。

 

PropertyEditor被限制為只能String<——>Object之間轉換,不能Object<——>Object,Spring3提供了更強大的型別轉換(TypeConversion)支援,它可以在任意物件之間進行型別轉換,不僅僅是String<——>Object。