前面关于 Controller 相关的知识,我们已经学习完了。今天我将分享一下关于 SpringMVC 中的数据转换,数据绑定和数据验证。
更多精彩内容请看 web 前端中文站
http://www.lisa33xiaoq.net 可按 Ctrl + D 进行收藏
先来看一张 SpringMVC 处理数据请求的流程图。
上图中的流程说明:
- 首先创建数据绑定器,在此此会创建 ServletRequestDataBinder 类的对象,并设置 messageCodesResolver(错误码解析器);
- 提供第一个扩展点,初始化数据绑定器,在此处我们可以覆盖该方法注册自定义的 PropertyEditor(请求参数——>命令对象属性的转换);
- 进行数据绑定,即请求参数——>命令对象的绑定;
- 提供第二个扩展点,数据绑定完成后的扩展点,此处可以实现一些自定义的绑定动作;
- 验证器对象的验证,验证器通过 validators 注入,如果验证失败,需要把错误信息放入 Errors(此处使用 BindException 实现);
- 提供第三个扩展点,此处可以实现自定义的绑定/验证逻辑;
- 将 errors 传入功能处理方法进行处理,功能方法应该判断该错误对象是否有错误进行相应的处理。
数据类型转换
请求参数(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 对应的值 |
接下来我们写自定义的属性编辑器进行数据绑定。模型对象:
package com.lisa33xiaoq.net.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 com.lisa33xiaoq.net.chapter4.model; //如格式 010-12345678 public class PhoneNumberModel { private String areaCode;//区号 private String phoneNumber;//电话号码 //省略 getter/setter }
PhoneNumber 属性编辑器
前台输入如 010-12345678 自动转换为 PhoneNumberModel。
package com.lisa33xiaoq.net.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。
需要在控制器注册我们自定义的属性编辑器。
此处我们使用 AbstractCommandController,因为它继承了 BaseCommandController,拥有绑定流程。
package com.lisa33xiaoq.net.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 进行类型转换;
spring 配置文件 chapter4-servlet.xml
<bean name="/dataBind" class="com.lisa33xiaoq.net.chapter4.web.controller.DataBinderTestController"/>
视图页面(WEB-INF/jsp/bindAndValidate/success.jsp)
EL phoneNumber:${dataBinderTest.phoneNumber}<br/> EL state:${dataBinderTest.state}<br/> EL date:${dataBinderTest.date}<br/>
下面进入测试部分,在浏览器地址栏输入请求的 URL,如:
控制器输出的内容:
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
使用 WebDataBinder 进行控制器级别注册 PropertyEditor(控制器独享),使用 WebDataBinder 注册控制器级别的 PropertyEditor,这种方式注册的 PropertyEditor 只对当前控制器独享,即其他的控制器不会自动注册这个 PropertyEditor,如果需要还需要再注册一下。
使用 WebBindingInitializer 批量注册 PropertyEditor,如果想在多个控制器同时注册多个相同的 PropertyEditor 时,可以考虑使用 WebBindingInitializer。
实现 WebBindingInitializer。
package com.lisa33xiaoq.net.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。
修改 DataBinderTestController,注释掉 initBinder 方法;
修改 chapter4-servlet.xml 配置文件:
<!-- 注册 WebBindingInitializer 实现 --> <bean id="myWebBindingInitializer" class="com.lisa33xiaoq.net.chapter4.web.controller.support.initializer.MyWebBindingInitializer"/> <bean name="/dataBind" class="com.lisa33xiaoq.net.chapter4.web.controller.DataBinderTestController"> <!-- 注入 WebBindingInitializer 实现 --> <property name="webBindingInitializer" ref="myWebBindingInitializer"/> </bean>
使用 WebBindingInitializer 的好处是当你需要在多个控制器中需要同时使用多个相同的 PropertyEditor 可以在 WebBindingInitializer 实现中注册,这样只需要在控制器中注入 WebBindingInitializer 即可注入多个 PropertyEditor。
全局级别注册 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 个字符(超长了)那又该怎么处理呢?出错了需要错误消息,那错误消息应该是硬编码?还是可配置呢?
接下来我们来学习一下数据验证器进行数据验证吧。
【注:本文源自网络文章资源,由站长整理发布】