使用SpringMVC中的PropertyEditor实现数据类型转换和数据验证

JAVA herman 746浏览 0评论
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

前面关于Controller相关的知识,我们已经学习完了。今天我将分享一下关于SpringMVC中的数据转换,数据绑定和数据验证。

先来看一张SpringMVC处理数据请求的流程图。

PropertyEditor

上图中的流程说明:

  1. 首先创建数据绑定器,在此此会创建ServletRequestDataBinder类的对象,并设置messageCodesResolver(错误码解析器);
  2. 提供第一个扩展点,初始化数据绑定器,在此处我们可以覆盖该方法注册自定义的PropertyEditor(请求参数——>命令对象属性的转换);
  3. 进行数据绑定,即请求参数——>命令对象的绑定;
  4. 提供第二个扩展点,数据绑定完成后的扩展点,此处可以实现一些自定义的绑定动作;
  5. 验证器对象的验证,验证器通过validators注入,如果验证失败,需要把错误信息放入Errors(此处使用BindException实现);
  6. 提供第三个扩展点,此处可以实现自定义的绑定/验证逻辑;
  7. 将errors传入功能处理方法进行处理,功能方法应该判断该错误对象是否有错误进行相应的处理。

数据类型转换

请求参数(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对应的值

接下来我们写自定义的属性编辑器进行数据绑定。模型对象:

package com.xttblog.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.xttblog.chapter4.model;  
//如格式010-12345678  
public class PhoneNumberModel {  
    private String areaCode;//区号  
    private String phoneNumber;//电话号码  
    //省略getter/setter  
}  

PhoneNumber属性编辑器

前台输入如010-12345678自动转换为PhoneNumberModel。

package com.xttblog.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.xttblog.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.xttblog.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,如:

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

控制器输出的内容:

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.xttblog.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.xttblog.chapter4.web.controller.support.initializer.MyWebBindingInitializer"/>  
<bean name="/dataBind" class="com.xttblog.chapter4.web.controller.DataBinderTestController">  
    <!-- 注入WebBindingInitializer实现 -->  
    <property name="webBindingInitializer" ref="myWebBindingInitializer"/>  
</bean>

使用WebBindingInitializer的好处是当你需要在多个控制器中需要同时使用多个相同的PropertyEditor可以在WebBindingInitializer实现中注册,这样只需要在控制器中注入WebBindingInitializer即可注入多个PropertyEditor。

全局级别注册PropertyEditor(全局共享)

只需要将我们自定义的PropertyEditor放在和你的模型类同包下即可,且你的Editor命名规则必须是“模型类名Editor”,这样Spring会自动使用标准JavaBean架构进行自动识别,如图所示:

全局级别注册PropertyEditor(全局共享)

此时我们把“DataBinderTestController”的“binder.registerCustomEditor(PhoneNumberModel.class, new PhoneNumberEditor());”注释掉,再尝试访问上面实例中的测试URL即可成功。

这种方式不仅仅在使用Spring时可用,在标准的JavaBean等环境都是可用的,可以认为是全局共享的(不仅仅是Spring环境)。

PropertyEditor被限制为只能String<——>Object之间转换,不能Object<——>Object,Spring3提供了更强大的类型转换(TypeConversion)支持,它可以在任意对象之间进行类型转换,不仅仅是String<——>Object。
 
如果我在地址栏输入错误的数据,即数据绑定失败,Spring Web MVC该如何处理呢?如果我输入的数据不合法呢?如用户名输入100个字符(超长了)那又该怎么处理呢?出错了需要错误消息,那错误消息应该是硬编码?还是可配置呢?
 
接下来我们来学习一下数据验证器进行数据验证吧。

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » 使用SpringMVC中的PropertyEditor实现数据类型转换和数据验证