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

前言

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

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

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

这里面有三个问题:

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

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

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

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

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

前提

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

JavaBean是一个简单类,无参构造,命名惯例(SETTER/GETTER) -- 其标准由Oracle提供!详见 JavaBeans 或 JavaBean 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,代码如下:

1 public class Person {
2
3 private String name;
4 private int age;
5
6 // getters and setters...略
7 }

 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

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

 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

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。

例子,先来两个类:

 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大概类似这样:

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):

 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)会是非常方便的(--暂时不明白,以后再来看吧)。

见下例:

 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官方文档学习(一)

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

随机推荐

  1. Spark的基本说明

    1.关于Application 用户程序,一个Application由一个在Driver运行的功能代码和多个Executor上运行的代码组成(工作在不同的节点上). 又分成多个Job,每个Job由多个 ...

  2. C++面向对象程序设计的一些知识点(4)

    摘要:累继承可以实现源代码重用,模板技术也可以达到同样的目的.模板可以分为函数模板和类模板,其共同点是允许参数类进行类型化,编译器根据程序员传递的具体类型,又把函数模板实例化为具体函数,把类模板实例化 ...

  3. 交叉编译busybox

    交叉编译器: 3.3.2# wget -c http://www.busybox.net/downloads/busybox-1.7.0.tar.bz2# tar jxvf busybox-1.7.0 ...

  4. strcat

    将两个char类型链接. char d[20]="GoldenGlobal"; char *s="View"; strcat(d,s); 结果放在d中 prin ...

  5. jquery超炫的列表筛选插件

    今天要为大家带来一款非常实用的jquery列表筛选插件,效果非常好.单击某项的时候动画筛选该项的列表.我们一起看下效果图: 在线预览   源码下载 我们一起看下实现的代码: html代码: <d ...

  6. 从c++看js的那些概念

    感觉c++逐渐没落了,js正风生水起,各种概念层出不穷. reactjs,flux ,angularjs cmd,amd模块化设计. 初看起来好像很高端的样子,后来发现也不过是如此. c++毕竟经历了 ...

  7. 通过SiteMapDataSource动态获取SiteMap文件进行权限设置

    最近做一个用ASP.NET做一个小项目,用户不是很多,功能不算太复杂,但是做到权限控制的时候有点犯难,这么一个小系统如果全部做一个大的复杂的权限控制觉得成本不划算,打算用Treeview ,根据不同的 ...

  8. java日期工具类DateUtil-续二

    该版本是一次较大的升级,农历相比公历复杂太多(真佩服古人的智慧),虽然有规律,但涉及到的取舍.近似的感念太多,况且本身的概念就已经很多了,我在网上也是查阅了很多的资料,虽然找到一些计算的方法,但都有些 ...

  9. 常用sqoop操作

    1. 关系型数据库到hive sqoop import --connect jdbc:mysql://localhost:3306/datahouse --username datahs --pass ...

  10. import_tasks: tasks/sometasks.yml

    - import_tasks: tasks/sometasks.yml  when: "'reticulating splines' in output" unarchive模块用 ...