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

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

前言

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

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

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

      这里面有三个问题:

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

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

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

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

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

前提

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

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

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

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

 

概览

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

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

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

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

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

ValidatorDataBinder都在org.springframework.validation包中。

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

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

注意到没有,这三者其实都是在操作JavaBeanProperty

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

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

另,PropertyEditorJavaBeans 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 }
View Code
 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 }
View Code

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

2、Resolving code to error message

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

当我们直接或间接的调用Errorsreject方法时,其实现不仅会注册我们传入的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”。

更多策略见MessageCodesResolverDefaultMessageCodesResolver的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一般不直接用在代码中,而是用在DataBinderBeanFactory 中。

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

下面讲一下其具体功能:

3.1、Setting and getting basic and nested properties

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

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

Expression解释
nameProperty name。
account.namenested 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
View Code

3.2、内建的PropertyEditor实现

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

Spring是利用这个概念进行ObjectString之间的转换而已。但它本身是个接口(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注册。
StringTrimmerEditortrim string,且可选将empty string转成null。默认没有注册!
URLEditor默认被BeanWrapperImpl注册。

Spring使用 java.beans.PropertyEditorManager 来设置搜索路径,搜索路径默认包含了sun.bean.editors -- 这里有针对FontColor以及大多数常见类型的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());
        }
    }
}
View Code

补充:

java.beans.BeanInfo 接口,用于提供beanmethod、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 }
View Code

下面就会调用幕后的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 }
View Code

关键是,如何将该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 }
View Code

然后,配置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 }
View Code

这种风格的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是最早的类型转换,但仅限于ObjectString之间。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工厂方法代替。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值