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

本篇太乱,请移步: 

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

 

 


 

写了删删了写,反复几次,对自己的描述很不满意,表达能力还是不行啊。另外,格式太花了,很不喜欢。

前提

1、什么是JavaBean?

简单类,无参构造,有SETTER/GETTER,其对应的字段称为属性。--其实还有isXxx,用于布尔类型的属性,略。

详见 https://en.wikipedia.org/wiki/JavaBeans

注意,这里的JavaBean不是Spring的bean

好了,现在已经有了属性(property的概念,下面所有的东西都是围绕它来进行的

2、需求

我们应该明白一点,所有的技术都是源自需求而出现的(不严谨)。搞明白需求就搞明白了它们的应用场景。

 

假定我们有一个桌面应用(下文用 app 代替 桌面应用),需要你输入生日,然后该app会输出你的年龄。看起来很简单吧,app的后台逻辑只要用当前日期减去你的生日即可,但是,现实不是这样子的。

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

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

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

看到这里,你应该已经搞明白Validation、Data Binding、Type Conversion三者之间的关系了。

 

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

 

简介

1、什么是Validation?

Validation 就是对属性的值进行校验。--【谁的属性?JavaBean的!】

例如,User的属性age,我的预期是[0, 100],其他所有值都是非法的,那就校验一下好了。

Spring提供了Validator 接口,能在应用的任意layer使用,来完成Validation的工作。

2、什么是数据绑定?

数据绑定就是将数据绑定到属性上!--【谁的属性?JavaBean的!】

两种方法,通过对象调用SETTER,或者,通过反射调用对象的SETTER,二者都是给属性设值。--谁的对象?JavaBean的!

例如,user.setAge(20)。或者通过反射 -- 在各种框架中常用。

Spring提供了DataBinder 来做具体的Data binding工作。

注意Validator 和DataBinder 构成了validation 包。

3、什么是类型转换?

将一个类型的对象转成另一个类型的对象,例如StringDate互转等。 --【谁的类型?属性的!】

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

注意PropertyEditor 概念是 JavaBeans specification 的一部分。

 
注意上面【】中的内容,这三者其实都是在操作JavaBean的属性
那么,问题又来了,Spring用什么来操作JavaBean的属性?答案是BeanWrapper ,这是一个接口,Spring还提供了一个实现BeanWrapperImpl 。但是,这个东西是很底层的概念,用户一般不必直接使用它,了解即可。
Spring的 DataBinder 和底层 BeanWrapper 都是使用 PropertyEditors 来解析和格式化属性值。
Spring 3 引入了 core.convert 包和format 包,可以看作是 PropertyEditor的替代品,更简单。稍后谈。 
 
至此,我们已经对Spring的Validation、Data Binding、Type Conversion有了大致的了解,现在可以进入正文了。来看点具体的东西。

正文

1、使用Validator接口进行Validation

使用Spring的Validator 接口进行校验,该接口只有两个方法,support(..)用于判断是否支持某类型,validate(..)则进行校验。
Validator 接口形参是一个Errors 对象,当校验时,可以将失败结果报告给该对象。
用户可以通过 implement 该接口来自定义Validator。另外,还有一个ValidationUtils的工具类。
 
下面通过一个例子来学习一下如何自定义Validator
public class Person {

    private String name;
    private int age;

    // the usual getters and setters...略
}
public class PersonValidator implements Validator {

    public boolean supports(Class clazz) {
        return Person.class.equals(clazz); // 仅支持Person类
    }

    public void validate(Object obj, Errors e) {
        ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
        Person p = (Person) obj;
        if (p.getAge() < 0) { // 年龄不能小于0
            e.rejectValue("age", "negativevalue");
        } else if (p.getAge() > 110) { // 年龄不能大于110
            e.rejectValue("age", "too.darn.old");
        }
    }
}

上面的代码一目了然,实现Validator 接口,完成两个方法即可。

如果是复合类(包含其他类字段),还可以注入相应的Validator -- 复用,高效。

2、错误码与错误消息(暂略)

上面提到了数据绑定和校验,还有一件事需要注意,这就是校验的错误信息的输出。 
使用MessageSource来输出error msg?(没看懂,暂略)
 
MessageCodesResolver 
DefaultMessageCodesResolver 

3、Bean操作 和 BeanWrapper

org.springframework.beans 包遵守Oracle提供的JavaBean标准。
 
beans包中一个非常重要的类是BeanWrapper 接口和其实现 BeanWrapperImpl 。
顾名思义,BeanWrapper 就是封装一个bean,对其进行操作,例如set/get property。支持嵌套属性等特点,详见 table 9.1
BeanWrapper 支持添加标准JavaBean PropertyChangeListeners and VetoableChangeListeners的能力,无须在目标类中编码。(监听器)
但是,一般不直接用在应用的代码中,而是用在 the DataBinder and the BeanFactory 中。
 

3.1、set/get属性

示例如下:
BeanWrapper company = new BeanWrapperImpl(new Company());

// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");

// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);

// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());

// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");

3.2、内建的PropertyEditor实现

Spring使用PropertyEditor来实现 an Object and a String之间的转换。-- 注意,是对象和字符串之间的转换,不是任意类型间的转换。
有时候,以不同形式描述对象中的properties很有用。例如,一个Date 可以用人类可读的方式来描述。这可以通过注册自定义的java.beans.PropertyEditor类型的editor来实现(--卧槽,刚意识到这不是Spring的东西),嗯,注册在BeanWrapper 上或者IoC容器中。详见Javadoc。
 
两个使用PropertyEditor 的例子:
① 你在xml中定义的bean,其class属性实际上是通过 ClassEditor 来转换成相应的类。
② Spring MVC中解析各种HTTP请求的参数,也是通过各种PropertyEditors 来完成的。 
 
Spring提供了大量的PropertyEditors ,它们都在org.springframework.beans.propertyeditors 包中。它们的多数,默认已经被BeanWrapperImpl注册了。当然,你也可以注册自己的变体来覆盖默认的:

Table 9.2. Built-in PropertyEditors

ClassExplanation

ByteArrayPropertyEditor

Editor for byte arrays. Strings will simply be converted to their corresponding byte representations. Registered by default byBeanWrapperImpl.

ClassEditor

Parses Strings representing classes to actual classes and the other way around. When a class is not found, anIllegalArgumentException is thrown. Registered by default by BeanWrapperImpl.

CustomBooleanEditor

Customizable property editor for Boolean properties. Registered by default by BeanWrapperImpl, but, can be overridden by registering custom instance of it as custom editor.可被覆盖。

CustomCollectionEditor

Property editor for Collections, converting any source Collection to a given target Collection type.

CustomDateEditor

Customizable property editor for java.util.Date, supporting a custom DateFormat. NOT registered by default. Must be user registered as needed with appropriate format.默认没注册!

CustomNumberEditor

Customizable property editor for any Number subclass like IntegerLongFloatDouble. Registered by default byBeanWrapperImpl, but can be overridden by registering custom instance of it as a custom editor.可被覆盖。

FileEditor

Capable of resolving Strings to java.io.File objects. Registered by default by BeanWrapperImpl.

InputStreamEditor

One-way property editor, capable of taking a text string and producing (via an intermediate ResourceEditor and Resource) an InputStream, so InputStream properties may be directly set as Strings. Note that the default usage will not close theInputStream for you! Registered by default by BeanWrapperImpl.默认不关闭InputStream!

LocaleEditor

Capable of resolving Strings to Locale objects and vice versa (the String format is [country][variant], which is the same thing the toString() method of Locale provides). Registered by default by BeanWrapperImpl.

PatternEditor

Capable of resolving Strings to java.util.regex.Pattern objects and vice versa.

PropertiesEditor

Capable of converting Strings (formatted using the format as defined in the javadocs of the java.util.Properties class) toProperties objects. Registered by default by BeanWrapperImpl.

StringTrimmerEditor

Property editor that trims Strings. Optionally allows transforming an empty string into a null value. NOT registered by default; must be user registered as needed.将空字符串转成null。默认没注册!!!

URLEditor

Capable of resolving a String representation of a URL to an actual URL object. Registered by default by BeanWrapperImpl.

 
Spring 使用 java.beans.PropertyEditorManager 来设置搜索路径,以查找需要的property editors。
该路径也包含了sun.bean.editors,-- 这里有针对FontColor以及大多数基本类型的PropertyEditor 的实现。
注意,① 标准JavaBean设施会自动发现PropertyEditor 类(不需要显式的注册它们)--如果它们和它们处理的类在相同包下,且其名字以被其处理类的名字加上“Editor”的话。
例如:
1 com
2   chank
3     pop
4       Foo
5       FooEditor // the PropertyEditor for the Foo class

注意,② 还可以使用标准BeanInfo JavaBean机制。下例就使用了该机制来显式注册相关类属性的一个或多个PropertyEditor 实例。(使用已有的editor)

1 com
2   chank
3     pop
4       Foo
5       FooBeanInfo // the BeanInfo for the Foo class
 1 public class FooBeanInfo extends SimpleBeanInfo {
 2 
 3     public PropertyDescriptor[] getPropertyDescriptors() {
 4         try {
 5             final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
 6             PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Foo.class) {
 7                 public PropertyEditor createPropertyEditor(Object bean) {
 8                     return numberPE;
 9                 };
10             };
11             return new PropertyDescriptor[] { ageDescriptor };
12         }
13         catch (IntrospectionException ex) {
14             throw new Error(ex.toString());
15         }
16     }
17 }

3.2.1、注册额外定制的PropertyEditor

最笨且最不推荐的方法:使用 ConfigurableBeanFactory 接口的 registerCustomEditor() 方法,前提是拥有BeanFactory引用。
稍微方便点的方法:使用一个特殊的bean factory post-processor --- CustomEditorConfigurer。虽然可以在 BeanFactory 实现中使用,但更建议在ApplicationContext中使用。
注意,所有 bean factories和application contexts都会自动应用大量的内建property editors -- 通过BeanWrapper 来处理property conversions。(前面有提到,BeanWrapperImpl 会自动注册一些内建的editors)。此外,具体的ApplicationContexts 还会覆盖或者添加额外的editors。(这句很重要)
 
例子,假定有两个类:
 1 package example;
 2 
 3 public class ExoticType {
 4 
 5     private String name;
 6 
 7     public ExoticType(String name) {
 8         this.name = name;
 9     }
10 }
11 
12 public class DependsOnExoticType {
13 
14     private ExoticType type;
15 
16     public void setType(ExoticType type) {
17         this.type = type;
18     }
19 }

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

<bean id="sample" class="example.DependsOnExoticType">
    <property name="type" value="aNameForExoticType"/>
</bean>

该editor实现大概类似这样:

1 // 将String转成ExoticType对象
2 package example;
3 
4 public class ExoticTypeEditor extends PropertyEditorSupport {
5 
6     public void setAsText(String text) {
7         setValue(new ExoticType(text.toUpperCase()));
8     }
9 }

然后,就到了最后一步,也是这里的主题,使用CustomEditorConfigurer 将新的PropertyEditor 注册到ApplicationContext

如下:

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

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

提醒,下面这段话可能比较绕,建议略过,直接看代码,简洁明了。

PropertyEditorRegistrars 配合接口 PropertyEditorRegistry 使用。这个接口被BeanWrapper(还有DataBinder)实现了。
PropertyEditorRegistrars 配合CustomEditorConfigurer 使用时特别方便,后者有一个方法setPropertyEditorRegistrars(..),以这种方式添加到CustomEditorConfigurer 中的PropertyEditorRegistrars 可以轻松的共享给DataBinder 和Spring MVC Controllers。 Furthermore, it avoids the need for synchronization on custom editors:每个bean创建时,PropertyEditorRegistrar 都会创建新的PropertyEditor 实例。
 
通过示例来学习最简单。
首先,创建你的PropertyEditorRegistrar 实现:
 1 package com.foo.editors.spring;
 2 
 3 public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
 4 
 5     public void registerCustomEditors(PropertyEditorRegistry registry) {
 6 
 7         // 需要PropertyEditor实例
 8         registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
 9 
10         // 可以注册任意多的PropertyEditor...
11     }
12 }

插一句org.springframework.beans.support.ResourceEditorRegistrar 也是一个实现,可以参考下。

其次,配置CustomEditorConfigurer ,注入我们的CustomPropertyEditorRegistrar :
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="propertyEditorRegistrars">
        <list>
            <ref bean="customPropertyEditorRegistrar"/>
        </list>
    </property>
</bean>

<bean id="customPropertyEditorRegistrar" class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>

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

 

 1 public final class RegisterUserController extends SimpleFormController {
 2 
 3     private final PropertyEditorRegistrar customPropertyEditorRegistrar;
 4 
 5     public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
 6         this.customPropertyEditorRegistrar = propertyEditorRegistrar;
 7     }
 8 
 9     protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) throws Exception {
10         this.customPropertyEditorRegistrar.registerCustomEditors(binder);
11     }
12 
13     // other methods to do with registering a User
14 }

 

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

 

4、Spring 类型转换

Spring 3 引入了core.convert 包,提供了通用的类型转换系统。该系统定义了一个SPI -- 实现了类型转换逻辑,还定义了一个API -- 用于在runtime执行类型转换。在Spring容器内,该系统可以用作PropertyEditors 的替代品来转换外部bean property value strings 到需要的property 类型。
插一句,SPI 是 Service Provider Interface,与API的区别见 difference-between-spi-and-api 。

4.1、Converter SPI

很简单:
1 package org.springframework.core.convert.converter;
2 
3 public interface Converter<S, T> {
4 
5     T convert(S source);
6 
7 }

想要创建自己的converter,实现这个接口即可。

该converter还能用于将一个S类型的集合(或数组)转成T类型的集合(或数组),前提是已经注册了一个代理数组/集合converter(DefaultConversionService 默认已注册了)。
 
core.convert.support 包中以提供了几个converter实现。它们包括从StringNumber和其他常见类型的转换。
 
来看一下StringToInteger :
1 package org.springframework.core.convert.support;
2 
3 final class StringToInteger implements Converter<String, Integer> {
4 
5     public Integer convert(String source) {
6         return Integer.valueOf(source);
7     }
8 
9 }

4.2、ConverterFactory

当你需要整个类层次上的转换逻辑时,例如,从String转成java.lang.Enum对象时,实现ConverterFactory即可:
1 package org.springframework.core.convert.converter;
2 
3 public interface ConverterFactory<S, R> {
4 
5     <T extends R> Converter<S, T> getConverter(Class<T> targetType);
6 
7 }

例子:

 1 package org.springframework.core.convert.support;
 2 
 3 final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
 4 
 5     public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
 6         return new StringToEnumConverter(targetType);
 7     }
 8 
 9     private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
10 
11         private Class<T> enumType;
12 
13         public StringToEnumConverter(Class<T> enumType) {
14             this.enumType = enumType;
15         }
16 
17         public T convert(String source) {
18             return (T) Enum.valueOf(this.enumType, source.trim());
19         }
20     }
21 }

4.3、GenericConverter(略)

4.4、ConditionalGenericConverter(略)

4.5、ConversionService API 转换服务API

ConversionService定义了一个统一的API以执行类型转换逻辑,是一个facade(门面)接口。
 1 package org.springframework.core.convert;
 2 
 3 public interface ConversionService {
 4     // 判断,能否将源类型转成目标类型
 5     boolean canConvert(Class<?> sourceType, Class<?> targetType);
 6     // 转换 
 7     <T> T convert(Object source, Class<T> targetType);
 8     // 判断
 9     boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
10     // 转换
11     Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
12 
13 }

从上面的代码可以推理,该接口的实现必定是先判断能否转换,如能转换再调用相应的converter进行转换。--问题,从哪调用converter?ConverterRegistry

所以多数 ConversionService的实现 也实现了 ConverterRegistry,它提供了一个 SPI 以注册 converters 。在内部,一个ConversionService的实现委托注册的converters来执行类型转换逻辑。

 
core.convert.support 包中提供了一个健全的实现。GenericConversionService 是一个通用的实现,适用于多数环境。ConversionServiceFactory 提供了一个便捷的工厂以创建常见的ConversionService配置。
 

4.6、Configuring a ConversionService 配置

一个ConversionService是一个无状态的对象,被设计成在应用启动时被实例化,然后在多个线程之间共享。
在一个Spring应用中,可以给每个容器配置一个ConversionService。Spring会加载并调用它。你也可以注入需要的地方,直接调用它。
注意:如果没有ConversionService被注册,会使用原始的基于PropertyEditor的系统
 
注册一个默认的ConversionService(id不可修改):
1 <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"/>

注意ConversionServiceFactoryBean的Javadoc说返回的conversionServiceDefaultConversionService 实例。

默认的ConversionService可以在strings、numbers、enums、collections、maps以及其他常用类型之间进行转换。

要补充或者覆盖默认的转换器,需要设置converters 属性。其值可以是ConverterConverterFactory或者GenericConverter的实现。
1 <bean id="conversionService"
2         class="org.springframework.context.support.ConversionServiceFactoryBean">
3     <property name="converters">
4         <set>
5             <bean class="example.MyCustomConverter"/>
6         </set>
7     </property>
8 </bean>

个人心得:

①、非web项目的Spring应用,不会自动注册ConversionService bean。(web项目暂时没测)

②、@Configuration class 中进行ConversionService bean定义时,有几个选择:使用其实现,如GenericConversionServiceDefaultConversionService 或者其他;使用ConversionServiceFactoryBean。区别在于,GenericConversionService 没有注册converters,DefaultConversionService 注册了很多converters,ConversionServiceFactoryBean 则提供了DefaultConversionService。-- 所以,一般情况下,直接使用ConversionServiceFactoryBean 即可。如下:

 1     /**
 2      * This implementation creates a DefaultConversionService. 
 3      * Subclasses may override createConversionService() in order to return a GenericConversionService instance of their choosing. 
 4      * 
 5      * 这个可以取代上面的DefaultConversionService,基本一致。
 6      * 
 7      * @return
 8      */
 9     @Bean
10     public ConversionServiceFactoryBean conversionService(){
11         return new ConversionServiceFactoryBean();
12     }
View Code

另,

①通常也可以在Spring MVC应用中使用ConversionService。待后续。See Section 22.16.3, “Conversion and Formatting” in the Spring MVC chapter.
②在特定环境下,可能希望在转换时应用格式。See Section 9.6.3, “FormatterRegistry SPI” for details on using FormattingConversionServiceFactoryBean.
 

4.7、编码使用ConversionService

注入使用即可。
 1 @Service
 2 public class MyService {
 3 
 4     @Autowired
 5     public MyService(ConversionService conversionService) {
 6         this.conversionService = conversionService;
 7     }
 8 
 9     public void doIt() {
10         this.conversionService.convert(...)
11     }
12 }

 

多数情况下不能用于复合类型,如集合。例如,如果需要编码转换List<Integer>到List<String>,需要提供源类型和目标类型的正式定义。
幸运的是,TypeDescriptor 提供了多种选项,使其变得简单直接:
1 DefaultConversionService cs = new DefaultConversionService();
2 
3 List<Integer> input = ....
4 cs.convert(input,
5     TypeDescriptor.forObject(input), // List<Integer> type descriptor
6     TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));

 

再次提醒DefaultConversionService 自动注册的converters,可以适用于大多数环境。其中包含了collection converters、scalar converters,以及基本的ObjectString的converter。同样的converters也可以使用DefaultConversionService 的static addDefaultConverters 注册到任意ConverterRegistry 中。
 
值类型的转换器,会被自动复用到值类型的arraycollection中,不必再去创建。 -- 值类型这个概念,应该就是基本类型吧。

5、Spring Field Formatting (Spring字段格式化)

上面有提到, core.convert 是一个通用目的的类型转换系统。提供了统一的ConversionService API和强类型的Converter SPI,以实现转换逻辑。Spring容器使用该系统来绑定bean property values。
此外,SpELDataBinder 使用该系统绑定 field values。例如,当SpEL需要强制 a Short to a Long来完成expression.setValue(Object bean, Object value)尝试时,core.convert系统负责执行该强制。
---------------------------------回顾结束,开始新内容-------------------------------- 
现在来考虑下典型客户环境的类型转换需求,例如web或desktop应用。在该环境下,一般是String和其他互转。此外,你还经常需要本地化String values。但通用的core.convert Converter SPI不能直接完成格式化需求。
为了直接完成格式化需求,Spring 3 引入了便捷的 Formatter SPI,其提供了PropertyEditors的简单直接的替换 -- 用于client环境。
 
总的来说,通用目的的类型转换逻辑时,使用Converter SPI;在client环境下工作,且需要解析和输出本地化 field values 时,使用Formatter SPI。 -- ConversionService 为二者都提供了统一的类型转换 API
个人体会:一个是粗粒度的,一个是细粒度的。
 

5.1、Formatter SPI

1 package org.springframework.format;
2 
3 public interface Formatter<T> extends Printer<T>, Parser<T> {
4 }
1 public interface Printer<T> {
2     String print(T fieldValue, Locale locale);
3 }
1 import java.text.ParseException;
2 
3 public interface Parser<T> {
4     T parse(String clientValue, Locale locale) throws ParseException;
5 }

 

要创建自己的Formatter,实现Formatter接口即可。注意异常和线程安全。
format 包的子包中提供了几个Formatter的实现。number,datetime,datetime.joda
参考DateFormatter 。
 1 package org.springframework.format.datetime;
 2 
 3 public final class DateFormatter implements Formatter<Date> {
 4 
 5     private String pattern;
 6 
 7     public DateFormatter(String pattern) {
 8         this.pattern = pattern;
 9     }
10 
11     public String print(Date date, Locale locale) {
12         if (date == null) {
13             return "";
14         }
15         return getDateFormat(locale).format(date);
16     }
17 
18     public Date parse(String formatted, Locale locale) throws ParseException {
19         if (formatted.length() == 0) {
20             return null;
21         }
22         return getDateFormat(locale).parse(formatted);
23     }
24 
25     protected DateFormat getDateFormat(Locale locale) {
26         DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
27         dateFormat.setLenient(false);
28         return dateFormat;
29     }
30 
31 }
View Code

 

5.2、注解驱动的Formatting

绑定一个注解到一个formatter,实现 AnnotationFormatterFactory 即可。详见这里。略。

5.2.1、Format Annotation API 

便携的格式化注解API存在于org.springframework.format.annotation包中。
Use @NumberFormat to format java.lang.Number fields. Use @DateTimeFormat to format java.util.Date, java.util.Calendar, java.util.Long, or Joda Time fields.
例子:
1 public class MyModel {
2 
3     @DateTimeFormat(iso=ISO.DATE)
4     private Date date;
5 
6 }
View Code

 

5.3、FormatterRegistry SPI

这是一个SPI,用于注册formatters和converters。其实现FormattingConversionService 适用于多数环境。
该实现可以编码式配置或者声明式配置,通过FormattingConversionServiceFactoryBean
另外,其还实现了ConversionService,所以可以配置和DataBinder、SpEL一起使用。
 

5.4、FormatterRegistrar SPI

通过FormatterRegistry注册formatters和converters。 -- 蛋疼,两个词分不清。
 

6、配置全局 date & time 格式

默认,如果没有使用注解@DateTimeFormat,会使用 DateFormat.SHORT 风格。当然,你可以定义自己的全局格式。
需要确保Spring没有注册默认的formatters,然后手动注册所有的formatters。使用 DateFormat.SHORT 或 DateFormatterRegistrar  -- 这取决于你是否使用Joda Time 库。
例如,
 1 @Configuration
 2 public class AppConfig {
 3 
 4     @Bean
 5     public FormattingConversionService conversionService() {
 6 
 7         // Use the DefaultFormattingConversionService but do not register defaults
 8         DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);
 9 
10         // Ensure @NumberFormat is still supported
11         conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
12 
13         // Register date conversion with a specific global format
14         DateFormatterRegistrar registrar = new DateFormatterRegistrar();
15         registrar.setFormatter(new DateFormatter("yyyyMMdd"));
16         registrar.registerFormatters(conversionService);
17 
18         return conversionService;
19     }
20 }
View Code

 

注意,如果使用Spring MVC,记住,需要显式的配置使用的conversion service。对于基于Java的@Configuration来说,这意味着继承WebMvcConfigurationSupport 并重写mvcConversionService()。对于XML来说,应该使用<mvc:annotation-driven>元素的'conversion-service'属性。See Section 22.16.3, “Conversion and Formatting” for details.
 

7、Spring Validation

Spring 3 引入了validation support的几个增强。第一,完整的支持JSR-303 Bean Validation API第二,编码式使用时,Spring的DataBinder现在可以validate objects,也可以bind to them。第三,Spring MVC现在支持声明式校验@Controller的输入

7.1、JSR-303 Bean Validation API概览

允许定义声明式校验限制。如:
 1 public class PersonForm {
 2 
 3     @NotNull
 4     @Size(max=64)
 5     private String name;
 6 
 7     @Min(0)
 8     private int age;
 9 
10 }

 

For general information on JSR-303/JSR-349, see the Bean Validation website. For information on the specific capabilities of the default reference implementation, see the Hibernate Validator documentation. 
 

7.2、配置一个Bean Validation Provider

Spring提供了对Bean Validation API的完整的支持。包括启动其实现(provider),并将其作为Spring的bean。
所以,需要validation时,可以注入a javax.validation.ValidatorFactory or javax.validation.Validator
使用LocalValidatorFactoryBean 来配置默认的Validator
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

上面,会引动Bean Validation使用其自身的启动机制来初始化。如果在classpath中有JSR-303/JSR-349 provider,如Hibernate Validator,会被自动探测到。

7.2.1、注入一个Validator

LocalValidatorFactoryBean implements both javax.validation.ValidatorFactory and javax.validation.Validator, as well as Spring’sorg.springframework.validation.Validator
可以注入这几个接口的任意一个引用来使用。
①如果倾向于使用Bean Validation API,直接注入javax.validation.Validator :
1 import javax.validation.Validator;
2 
3 @Service
4 public class MyService {
5 
6     @Autowired
7     private Validator validator;
8 
9 }

②如果倾向于使用Spring Validation API,可以注入org.springframework.validation.Validator

1 import org.springframework.validation.Validator;
2 
3 @Service
4 public class MyService {
5 
6     @Autowired
7     private Validator validator;
8 
9 }

 

7.2.2、配置自定义限制(Constraints

每个Bean Validation constraint都由两部分组成。首先,一个@Constraint注解,声明了the constraint和其可配置的properties。其次,javax.validation.ConstraintValidator接口的实现,实现了the constraint's behavior。为了将声明与实现关联起来,每个@Constraint注解引用了一个相关的ValidationConstraint实现类。在运行时,当一个ConstraintValidatorFactory在你的domain model中遇到constraint 注解时会实例化被引用的实现。
 
默认的,LocalValidatorFactoryBean 配置了一个SpringConstraintValidatorFactory ,其使用Spring来创建ConstraintValidator实例。
下面是一个例子,一个定制的 @Constraint 声明,紧接着相关联的ConstraintValidator 实现--使用Spring来依赖注入:
1 @Target({ElementType.METHOD, ElementType.FIELD})
2 @Retention(RetentionPolicy.RUNTIME)
3 @Constraint(validatedBy=MyConstraintValidator.class)
4 public @interface MyConstraint {
5 }
View Code
1 import javax.validation.ConstraintValidator;
2 
3 public class MyConstraintValidator implements ConstraintValidator {
4 
5     @Autowired;
6     private Foo aDependency;
7 
8     ...
9 }
View Code

 

As you can see, a ConstraintValidator implementation may have its dependencies @Autowired like any other Spring bean.
 

7.2.3、Spring-driven Method Validation

方法验证,由Bean Validation 1.1 支持,作为一个自定义的扩展同样也被Hibernate Validator 4.3支持,通过一个 MethodValidationPostProcessor  bean定义,它可以被集成到Spring context中。
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

为了支持Spring-driven method validation,所有的目标类都需要使用Spring的@Validated注解,可以选择声明校验组。详见MethodValidationPostProcessor的javadoc。

7.2.4、更多配置选项(略)

 

7.3、配置一个DataBinder

从Spring 3开始,可以通过一个Validator来配置一个DataBinder实例。一旦配置了,就可以通过调用binder.validate()来调用Validator。所有校验Errors会自动添加到binderBindingResult
当编码式使用DataBinder时,这样可以用于在绑定后调用校验逻辑:
 1 Foo target = new Foo();
 2 DataBinder binder = new DataBinder(target);
 3 binder.setValidator(new FooValidator());
 4 
 5 // bind to the target object
 6 binder.bind(propertyValues);
 7 
 8 // validate the target object
 9 binder.validate();
10 
11 // get BindingResult that includes any validation errors
12 BindingResult results = binder.getBindingResult();
View Code

 

一个DataBinder也可以配置多个Validator实例 -- 通过dataBinder.addValidatorsdataBinder.replaceValidators
 
 
 
 
 
 
 
 
 

 

posted on 2016-10-10 10:18  LarryZeal  阅读(3044)  评论(1编辑  收藏  举报

导航