前言
在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 不是同一个概念!
嗯嗯,有时间研究下JavaBean spec。
概览
现在我们来看看具体的定义以及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,代码如下:
[][][][][][] olw
注意,如果是复合类的校验,还可以注入已有的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 |
这一小节不提供源码阅读,因为我们基本用不到它。仅作了解即可。
3.2、内建的PropertyEditor实现
必须再说一遍,PropertyEditor是JavaBeans specification的一部分! 全限定名:java.beans.PropertyEditor。
其本身是个接口(就是抽象啦),所以需要提供实现以供使用。于是Spring就提供了一堆实现。