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。
- 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,擁有綁定流程。
- 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配置文件:
- <!-- 註冊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。
如果我在地址欄輸入錯誤的數據,即數據綁定失敗,Spring Web MVC該如何處理呢?如果我輸入的數據不合法呢?如用戶名輸入100個字符(超長了)那又該怎麼處理呢?出錯了需要錯誤消息,那錯誤消息應該是硬編碼?還是可配置呢?
接下來我們來學習一下數據驗證器進行數據驗證吧。