第四章 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 當類沒有發現丟擲 |
√ |
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、模型物件:
- package cn.javass.chapter4.model;
- //省略import
- public class DataBinderTestModel {
- private String username;
- private boolean bool;//Boolean值測試
- private SchoolInfoModel schooInfo;
- private List hobbyList;//集合測試,此處可以改為陣列/Set進行測試
- private Map map;//Map測試
- private PhoneNumberModel phoneNumber;//String->自定義物件的轉換測試
- private Date date;//日期型別測試
- private UserState state;//String——>Enum型別轉換測試
- //省略getter/setter
- }
- package cn.javass.chapter4.model;
- //如格式010-12345678
- public class PhoneNumberModel {
- private String areaCode;//區號
- private String phoneNumber;//電話號碼
- //省略getter/setter
- }
(2、PhoneNumber屬性編輯器
前臺輸入如010-12345678自動轉換為PhoneNumberModel。
java程式碼: Java程式碼
- package cn.javass.chapter4.web.controller.support.editor;
- //省略import
- public class PhoneNumberEditor extends PropertyEditorSupport {
- Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$");
- @Override
- public void setAsText(String text) throws IllegalArgumentException {
- if(text == null || !StringUtils.hasLength(text)) {
- setValue(null); //如果沒值,設值為null
- }
- Matcher matcher = pattern.matcher(text);
- if(matcher.matches()) {
- PhoneNumberModel phoneNumber = new PhoneNumberModel();
- phoneNumber.setAreaCode(matcher.group(1));
- phoneNumber.setPhoneNumber(matcher.group(2));
- setValue(phoneNumber);
- } else {
- throw new IllegalArgumentException(String.format("型別轉換失敗,需要格式[010-12345678],但格式是[%s]", text));
- }
- }
- @Override
- public String getAsText() {
- PhoneNumberModel phoneNumber = ((PhoneNumberModel)getValue());
- return phoneNumber == null ? "" : phoneNumber.getAreaCode() + "-" + phoneNumber.getPhoneNumber();
- }
- }
PropertyEditorSupport:一個PropertyEditor的支援類;
setAsText:表示將String——>PhoneNumberModel,根據正則表示式進行轉換,如果轉換失敗丟擲異常,則接下來的驗證器會進行驗證處理;
getAsText:表示將PhoneNumberModel——>String。
(3、控制器
需要在控制器註冊我們自定義的屬性編輯器。
此處我們使用AbstractCommandController,因為它繼承了BaseCommandController,擁有繫結流程。
java程式碼: Java程式碼
- package cn.javass.chapter4.web.controller;
- //省略import
- public class DataBinderTestController extends AbstractCommandController {
- public DataBinderTestController() {
- setCommandClass(DataBinderTestModel.class); //設定命令物件
- setCommandName("dataBinderTest");//設定命令物件的名字
- }
- @Override
- protected ModelAndView handle(HttpServletRequest req, HttpServletResponse resp, Object command, BindException errors) throws Exception {
- //輸出command物件看看是否繫結正確
- System.out.println(command);
- return new ModelAndView("bindAndValidate/success").addObject("dataBinderTest", command);
- }
- @Override
- protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
- super.initBinder(request, binder);
- //註冊自定義的屬性編輯器
- //1、日期
- DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- CustomDateEditor dateEditor = new CustomDateEditor(df, true);
- //表示如果命令物件有Date型別的屬性,將使用該屬性編輯器進行型別轉換
- binder.registerCustomEditor(Date.class, dateEditor);
- //自定義的電話號碼編輯器
- binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());
- }
- }
initBinder:第一個擴充套件點,初始化資料繫結器,在此處我們註冊了兩個屬性編輯器;
CustomDateEditor:自定義的日期編輯器,用於在String<——>日期之間轉換;
binder.registerCustomEditor(Date.class, dateEditor):表示如果命令物件是Date型別,則使用dateEditor進行型別轉換;
PhoneNumberEditor:自定義的電話號碼屬性編輯器用於在String<——> PhoneNumberModel之間轉換;
binder.registerCustomEditor(PhoneNumberModel.class, newPhoneNumberEditor()):表示如果命令物件是PhoneNumberModel型別,則使用PhoneNumberEditor進行型別轉換;
(4、spring配置檔案chapter4-servlet.xml
- <bean name="/dataBind"
- class="cn.javass.chapter4.web.controller.DataBinderTestController"/>
(5、檢視頁面(WEB-INF/jsp/bindAndValidate/success.jsp)
- EL phoneNumber:${dataBinderTest.phoneNumber}<br/>
- EL state:${dataBinderTest.state}<br/>
- 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
- package cn.javass.chapter4.web.controller.support.initializer;
- //省略import
- public class MyWebBindingInitializer implements WebBindingInitializer {
- @Override
- public void initBinder(WebDataBinder binder, WebRequest request) {
- //註冊自定義的屬性編輯器
- //1、日期
- DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- CustomDateEditor dateEditor = new CustomDateEditor(df, true);
- //表示如果命令物件有Date型別的屬性,將使用該屬性編輯器進行型別轉換
- binder.registerCustomEditor(Date.class, dateEditor);
- //自定義的電話號碼編輯器
- binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());
- }
- }
通過實現WebBindingInitializer並通過binder註冊多個PropertyEditor。
(2、修改【三、示例】中的DataBinderTestController,註釋掉initBinder方法;
(3、修改chapter4-servlet.xml配置檔案:
java程式碼: Java程式碼
- <!-- 註冊WebBindingInitializer實現 -->
- <bean id="myWebBindingInitializer" class="cn.javass.chapter4.web.controller.support.initializer.MyWebBindingInitializer"/>
- <bean name="/dataBind" class="cn.javass.chapter4.web.controller.DataBinderTestController">
- <!-- 注入WebBindingInitializer實現 -->
- <property name="webBindingInitializer" ref="myWebBindingInitializer"/>
- </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。