Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)

时间:2022-09-05 14:34:45

题外话:本篇是对之前那篇的重排版。并拆分成两篇,免得没了看的兴趣。

前言

在Spring Framework官方文档中,这三者是放到一起讲的,但没有解释为什么放到一起。大概是默认了读者都是有相关经验的人,但事实并非如此,例如我。好在闷着头看了一遍,又查资料又敲代码,总算明白了。

其实说穿了一文不值,我们用一个例子来解释:

假定,现有一个app,功能是接收你输入的生日,然后显示你的年龄。看起来app只要用当前日期减去你输入的日期就是年龄,应该很简单对吧?可惜事实不是这样的。

这里面有三个问题:

问题一:我们输入的永远是字符串,字符串需要转成日期格式才能被我们的app用使用。--对应 类型转换

问题二:我们输入的字符串转成的日期怎么给app后台逻辑使用? --对应 数据绑定

问题三:人的年龄是有限制的,不能为负数,不能太大(例如超过了200)。 --对应 校验

同样的问题也出现在浏览器与服务器的交互之中,因为请求与响应,大都是被解析成字符串。

现在,你应该已经明白Validation、Data Binding、Type Conversion三者之间的关系了,它们彼此独立,但又互相配合。

前提

在了解更多之前,你应该先知道两个关键的概念:JavaBeanProperty

JavaBean是一个简单类,无参构造,命名惯例(SETTER/GETTER) -- 其标准由Oracle提供!详见 JavaBeansJavaBean wiki

SETTER/GETTER 对应的部分称为Property(属性)。

另外,org.springframework.beans 包 遵守Oracle提供的JavaBean标准。但是JavaBean 和 Spring的bean 不是同一个概念!

概览

现在我们来看看具体的定义以及Spring中提供的工具:

Validation 校验:对Property进行校验。--【谁的Property?JavaBean的!】

Spring提供了Validator接口,可在任意layer使用。

Data Binding 数据绑定:将数据绑定到Property上。--【谁的Property?JavaBean的!】

Spring提供了DataBinder来完成具体的数据绑定工作。

Validator和DataBinder都在org.springframework.validation包中。

Type Conversion 类型转换:将一种类型的对象转成另一种类型的对象,例如String与Date之间。--【谁的类型?Property的!】

Spring提供了PropertyEditors 以及core.convert 包和format 包。后两者是Spring 3 引入的,可以看作PropertyEditor 的替代品,更简单。

注意到没有,这三者其实都是在操作JavaBean的Property。

那么问题又来了,Spring如何操作JavaBean及其Property?答案是通过BeanWrapper接口和其实现BeanWrapperImpl,其内部通过PropertyEditors来解析和格式化Property。

BeanWrapper这个东西是很底层的概念,用户一般不必直接使用它,了解即可。

另,PropertyEditor是JavaBeans specification的一部分!

深入

下面来研究下Spring这些工具的具体用法:

1、Validator,查看源码可知,该接口只有两个方法,supports(Class<?> clazz)用于判断是否支持某类;validate(Object target, Errors errors)则用于校验,如有错误信息则报告给Errors -- 建议配合工具类ValidationUtils来使用。

实现该接口即可定义自己的Validator,代码如下:

Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)
1 public class Person {
2
3 private String name;
4 private int age;
5
6 // getters and setters...略
7 }
Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)
 1 public class PersonValidator implements Validator {
2
3 public boolean supports(Class clazz) {
4 return Person.class.equals(clazz); // 仅支持Person类
5 }
6
7 public void validate(Object obj, Errors e) {
8 ValidationUtils.rejectIfEmpty(e, "name", "name.empty"); // 使用工具类,效果和下面类似
9 Person p = (Person) obj;
10 if (p.getAge() < 0) { // 年龄不能小于0
11 e.rejectValue("age", "negativevalue");
12 } else if (p.getAge() > 110) { // 年龄不能大于110
13 e.rejectValue("age", "too.darn.old");
14 }
15 }
16 }

注意,如果是复合类的校验,还可以注入已有的Validator -- 复用、高效。

2、Resolving code to error message

如果我们想要通过MessageSource输出error message,我们会使用之前填入error code来索引。

当我们直接或间接的调用Errors的reject方法时,其实现不仅会注册我们传入的code,同时还会注册一些额外的error code。具体注册的error code是由MessageCodesResolver决定的。

默认情况下,会使用DefaultMessageCodesResolver,它不仅注册了你传入的code,还注册了字段名!

例如,你使用rejectValue(”age”, ”too.darn.old”),不仅会注册 ”too.darn.old”,还会注册 ”too.darn.old.age” 和 ”too.darn.old.age.int”。

更多策略见MessageCodesResolver和DefaultMessageCodesResolver的JavaDoc。

上面这两个,怎么说呢,没有涉及到反射之类的。这与下面要说的有所不同,提前说一下。

3、BeanWrapper,位于org.springframework.beans包中。

根据其JavaDoc可知,BeanWrapper提供的功能包括:set/get property values (单个/多个), get property descriptor, 以及查询判断property是可读的还是可写的。还支持nested property。还支持添加standard JavaBeans PropertyChangeListeners and VetoableChangeListeners,无需在目标类中编码(这不是废话么,类似aop的监听器)。最后还支持the setting of indexed properties。

BeanWrapper一般不直接用在代码中,而是用在DataBinder 和 BeanFactory 中。

另外,顾名思义,BeanWrapper的工作方式是wrap一个bean以执行操作。

下面讲一下其具体功能:

3.1、Setting and getting basic and nested properties

就是set/get property values(基本的和嵌套的),通过BeanWrapper的setPropertyValues()和getPropertyValues()方法完成 -- 详见JavaDoc。

这里需要重点了解的就是几个约定,例子如下:

Expression 解释
name Property name。
account.name nested property name of the property account
account[2] the 3rd element of the indexed property account
account[COMPANYNAME] map

因为我们基本用不到它,仅作了解即可,下面的代码可以略过。

Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)
 1 BeanWrapper company = new BeanWrapperImpl(new Company());
2
3 // 设置公司名字
4 company.setPropertyValue("name", "Some Company Inc.");
5
6 // 也可以这样做
7 PropertyValue value = new PropertyValue("name", "Some Company Inc.");
8 company.setPropertyValue(value);
9
10 // 创建director,绑到公司
11 BeanWrapper jim = new BeanWrapperImpl(new Employee());
12 jim.setPropertyValue("name", "Jim Stravinsky");
13 company.setPropertyValue("managingDirector", jim.getWrappedInstance());
14
15 // 获取公司中managingDirector的salary
16 Float salary = (Float) company.getPropertyValue("managingDirector.salary"); // nested property

3.2、内建的PropertyEditor实现

必须再说一遍,PropertyEditor是JavaBeans specification的一部分,不是Spring的东西! 其全限定名:java.beans.PropertyEditor。

Spring是利用这个概念进行Object与String之间的转换而已。但它本身是个接口(abstraction啦),所以Spring提供了一堆实现供大家使用 -- 需要注册到BeanWrapper或者IoC容器中。

下面是两个使用PropertyEditor的例子:

1,你在xml中定义的bean,其class属性是通过ClassEditor 来转成相应的类。

2,Spring MVC中对HTTP 请求的各种解析。

再来看看Spring提供的实现,它们位于org.springframework.beans.propertyeditors 包中。默认情况下,其中的多数都已由BeanWrapper注册了,可以直接使用。当然,你仍然可以注册自己的变体来覆盖掉默认的。--【这里有个很大的陷阱,所谓的注册,与ApplicationContext无关!!!】

如下:

Built-in PropertyEditors

解释
ByteArrayPropertyEditor 默认被BeanWrapperImpl注册。
ClassEditor 默认被BeanWrapperImpl注册。
CustomBooleanEditor 默认被BeanWrapperImpl注册。可被覆盖!
CustomCollectionEditor  
CustomDateEditor 默认没有注册!!!
CustomNumberEditor 默认被BeanWrapperImpl注册。可被覆盖!
FileEditor 默认被BeanWrapperImpl注册。
InputStreamEditor 默认被BeanWrapperImpl注册。默认不关闭InputStream!
LocaleEditor 默认被BeanWrapperImpl注册。
PatternEditor  
PropertiesEditor 默认被BeanWrapperImpl注册。
StringTrimmerEditor trim string,且可选将empty string转成null。默认没有注册!
URLEditor 默认被BeanWrapperImpl注册。

Spring使用 java.beans.PropertyEditorManager 来设置搜索路径,搜索路径默认包含了sun.bean.editors -- 这里有针对Font、Color以及大多数常见类型的PropertyEditor实现!

注意,standard JavaBeans infrastructure 会自动发现同路径下的PropertyEditor,前提是它们和对应的类名一致,且以’Editor’结尾。例如:

cn.larry.domain.User

cn.larry.domain.UserEditor //这个Editor会被自动发现。

还可以使用standard BeanInfo JavaBeans mechanism,注册一个或多个PropertyEditor。如下:

cn.larry.domain.Foo

cn.larry.domain.FooBeanInfo

Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)
public class FooBeanInfo extends SimpleBeanInfo {

    public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Foo.class) {
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
};
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}

补充:

java.beans.BeanInfo 接口,用于提供bean的method、property、events等信息。建议使用SimpleBeanInfo。

java.beans.Introspector先查找与target bean class同包路径下的BeanInfo(以BeanInfo结尾),如果没有,再查找每个包中是否存在。

-- 例如,对"sun.xyz.OurButton"来说,先查找"sun.xyz.OurButtonBeanInfo",如果失败再查找其他包中是否存在一个OurButtonBeanInfo class。

3.2.1、注册其他自定义的PropertyEditors

注意,这里的其他是指除了上面(3.2)提到的两种方式,你可以选择上面的方式,也可以选择这里的方式。

有三种方法:

a> 最笨且最不推荐的方法:使用 ConfigurableBeanFactory 接口的 registerCustomEditor() 方法,前提是拥有BeanFactory引用。

b> 稍微方便点的方法:使用一个特殊的bean factory post-processor --- CustomEditorConfigurer。虽然可以在BeanFactory 实现中使用,但更建议在ApplicationContext中使用。

注意,所有的bean factories 和 application contexts 都会自动应用大量的内建property editors。

前面有提到,BeanWrapperImpl会自动注册一些,此外,具体的ApplicationContext 还会覆盖或者添加额外的editors。

例子,先来两个类:

Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)
 1 package example;
2
3 public class Person {
4
5 private String name;
6
7 public Person(String name) {
8 this.name = name;
9 }
10 }
11
12 public class Team {
13
14 private Person person;
15
16 public void setPerson(Person person) {
17 this.person = person;
18 }
19 }

下面就会调用幕后的PropertyEditor --注意,这里的value是String,后台editor会将其转成 Person类型。

<bean id="sample" class="example.Team">
<property name="person" value="abc"/>
</bean>

该editor大概类似这样:

Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)
1 // 将String转成Person对象
2 package example;
3
4 public class PersonEditor extends PropertyEditorSupport {
5
6 public void setAsText(String text) {
7 setValue(new Person(text.toUpperCase()));
8 }
9 }

关键是,如何将该editor注册到ApplicationContext中:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.PersonType" value="example.PersonEditor"/>
</map>
</property>
</bean>

c> 使用PropertyEditorRegistrars,需要手动创建它。在复用时很有用,因为它打包了一组editor,拿来即用。(听起来,是类似map或者set之类的集合??)

直接上代码吧

首先,创建你的 PropertyEditorRegistrar (可以参考 org.springframework.beans.support.ResourceEditorRegistrar):

Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)
 1 package cn.larry.editors.spring;
2
3 public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
4
5 public void registerCustomEditors(PropertyEditorRegistry registry) {
6
7 // 需要PropertyEditor实例
8 registry.registerCustomEditor(Person.class, new PersonEditor());
9
10 // 可以注册任意多的PropertyEditor...
11 }
12 }

然后,配置CustomEditorConfigurer ,注入我们的CustomPropertyEditorRegistrar :

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customPropertyEditorRegistrar"/>
</list>
</property>
</bean> <bean id="customPropertyEditorRegistrar" class="cn.larry.editors.spring.CustomPropertyEditorRegistrar"/>

最后,在使用Spring MVC框架时,使用CustomPropertyEditorRegistrars 配合data-binding Controllers(如SimpleFormController)会是非常方便的(--暂时不明白,以后再来看吧)。

见下例:

Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)
 1 // 无语,Spring 4已经不再支持SimpleFormController了!!!使用@Controller代替
2 // 但是,仍然木有明白本类的作用!以及,为毛final???
3 // 有个带参构造,默认会调用这个创建实例,那么,注入的是???
4 // 也没见到@Autowired啊
5 // 另外,protected方法是干嘛的???
6 // -- 难道说,这个是给别的Controller调用的?
7 @Controller
8 public final class RegisterUserController /*extends SimpleFormController*/ {
9
10 private final PropertyEditorRegistrar customPropertyEditorRegistrar;
11
12 public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
13 this.customPropertyEditorRegistrar = propertyEditorRegistrar;
14 }
15
16 protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
17 this.customPropertyEditorRegistrar.registerCustomEditors(binder);
18 }
19
20 // other methods to do with registering a User
21 }

这种风格的PropertyEditor 注册能简洁代码(the implementation of initBinder(..) is just one line long!),且允许通用的PropertyEditor 注册代码包含在一个类中--然后由所有需要的Controllers 共享。(这个是重点吧???)

结束

为了限制篇幅长短,本篇到此为止,其他内容见下一篇。内容是:Spring Type Conversion(ConversionService)、Spring Field Formatting、globle date & time format、Spring Validation。

注意,

1、本篇提到的PropertyEditor是最早的类型转换,但仅限于Object与String之间。ConversionService则不限于此,更灵活方便,是PropertyEditor的替代品。

2、本篇只提到了Validator接口,但没提及如何集成到Spring中,下一篇会谈到。

下一篇:

Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(二)

20161013补充:

1,关于JavaBeans,请见我的另一篇文章:JavaBeans 官方文档学习

2,所谓的注册PropertyEditor,是不是在ApplicationContext注册bean!而是让standard JavaBeans infrastructure能够发现相应的PropertyEditor!

3,关于BeanWrapperImpl,从Spring 2.5起,主要限于内部使用。建议使用PropertyAccessorFactory.forBeanPropertyAccess工厂方法代替。

Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(一)的更多相关文章

  1. Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion(二)

    接前一篇 Spring Framework 官方文档学习(四)之Validation.Data Binding.Type Conversion(一) 本篇主要内容:Spring Type Conver ...

  2. Spring Framework 官方文档学习(四)之Validation、Data Binding、Type Conversion

    本篇太乱,请移步: Spring Framework 官方文档学习(四)之Validation.Data Binding.Type Conversion(一) 写了删删了写,反复几次,对自己的描述很不 ...

  3. Spring Framework 官方文档学习(二)之IoC容器与bean lifecycle

    到目前为止,已经看了一百页.再次感慨下,如果想使用Spring,那可以看视频或者找例子,但如果想深入理解Spring,最好还是看官方文档. 原计划是把一些基本接口的功能.层次以及彼此的关系罗列一下.同 ...

  4. Spring Framework 官方文档学习(一)介绍

    http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#overview-maven-bom ...

  5. Spring Framework 官方文档学习(三)之Resource

    起因 标准JDK中使用 java.net.URL 来处理资源,但有很多不足,例如不能限定classpath,不能限定 ServletContext 路径. 所以,Spring提供了 Resource ...

  6. Spring 4 官方文档学习(十二)View技术

    关键词:view technology.template.template engine.markup.内容较多,按需查用即可. 介绍 Thymeleaf Groovy Markup Template ...

  7. Spring 4 官方文档学习(十一)Web MVC 框架

    介绍Spring Web MVC 框架 Spring Web MVC的特性 其他MVC实现的可插拔性 DispatcherServlet 在WebApplicationContext中的特殊的bean ...

  8. Spring Boot 官方文档学习(一)入门及使用

    个人说明:本文内容都是从为知笔记上复制过来的,样式难免走样,以后再修改吧.另外,本文可以看作官方文档的选择性的翻译(大部分),以及个人使用经验及问题. 其他说明:如果对Spring Boot没有概念, ...

  9. Spring boot官方文档学习&lpar;一&rpar;

    个人说明:本文内容都是从为知笔记上复制过来的,样式难免走样,以后再修改吧.另外,本文可以看作官方文档的选择性的翻译(大部分),以及个人使用经验及问题. 其他说明:如果对Spring Boot没有概念, ...

随机推荐

  1. 二十七、EFW框架BS系统开发中的MVC模式探讨

    回<[开源]EFW框架系列文章索引>        EFW框架源代码下载V1.3:http://pan.baidu.com/s/1c0dADO0 EFW框架实例源代码下载:http://p ...

  2. 【WinAPI】User32&period;dll注释

    #region User32.dll 函数 /// <summary> /// 该函数检索一指定窗口的客户区域或整个屏幕的显示设备上下文环境的句柄,以后可以在GDI函数中使用该句柄来在设备 ...

  3. uiatuomator命令启动apk&comma;与查找多个相同控件

    背景:在做项目时,发现使用uiatuomator中遇到了一些问题,现在把解决方法和思路分享出来 案列1:使用命令去启动要运用的apk包 在做自动化时,需要通过命令去启动APK的包,我使用的是sdk中自 ...

  4. float与double剖析

    今天研究下float与double的编码 float: 我们来看一下这组数是如何一步步从16进制转换到float的 float编码格式: 1.将16进制转换到2进制 整理后:0 1000 0010 1 ...

  5. C&plus;&plus;面试宝典2011版

    1.new.delete.malloc.free关系 delete会调用对象的析构函数,和new相应free仅仅会释放内存,new调用构造函数.malloc与free是C++/C语言的标准库函数,ne ...

  6. jQuery UI 入门之实用实例

    jQuery UI 入门 jQuery UI 简介 jQuery UI 是一个建立在 jQuery JavaScript 库上的小部件和交互库,您可以使用它创建高度交互的 Web 应用程序.无论您是创 ...

  7. TensorFlow Object Detection API(Windows下测试)

    "Speed/accuracy trade-offs for modern convolutional object detectors." Huang J, Rathod V, ...

  8. 安卓recyclerView 分割线的那些事

    在这里我想记录下recyclerView 分割线遇到的一些问题,主要提供一些个人思路,代码可能不多~ 1.宽度问题 描述:我现在需要做一张卡片,卡片里面是一条条联系人,而且我们卡片外层是有阴影的,我的 ...

  9. Spring&plus;SpringMVC&plus;mybatis整合以及注解的使用&lpar;三&rpar;

    1.包结构:

  10. orcal数据库基本操作

    1.连接 SQL*Plus system/manager 2.显示当前连接用户 SQL> show user 3.查看系统拥有哪些用户 SQL> select * from all_use ...