正如在前面的小节中说的那样,core.convert是一个通用的类型转换系统。它提供了一个统一的ConversionServiceAPI和一个强类型的Converter SPI来实现类型转换逻辑。Spring容器使用这个系统来绑定bean的属性值。另外,SpEL和DataBinder也都利用这个系统来绑定字段值。比如,当SpEL需要将一个Short强转成Long来完成expression.setValue(Objectbean, Object value)的操作时,将会由core.convert系统来执行这次强转。
现在来考虑一个典型的客户环境(比如一个web或是桌面应用)的类型转换需求。在这样的环境中,你需要将String转换成其他类型来支持客户回传过程(译者注:也就是用户传值给服务器的过程),另外你也需要将它再转成String来支持页面渲染过程(译者注:也就是服务器将值传给用户的过程)。另外,通常你需要将String值本地化。更为通用的core.convert的Converter SPI并不直接支持这样的格式化需求。所以为了直接支持它,Spring3提出了一个方便的FormatterSPI,它作为PropertyEditors的替代,为客户环境提供了一个简单而健壮的实现。
总的来说,当你需要实现通用的类型转换逻辑时使用Converter SPI,比如java.utils.Date和java.lang.Long类型之间互相转换时。而当你处于客户端环境时,比如在web应用中,需要将字段值解析并以本地化格式输出时使用FormatterSPI。ConversionService为两种SPI提供了一个统一的类型转换API。
实现了字段格式化逻辑的Formatter SPI简单且是强类型的:
package org.springframework.format; public interface Formatter<T> extends Printer<T>, Parser<T> { }
Formatter继承了Printer和Parser接口这两个building-block(组成部分,很有意思的单词)
public interface Printer<T> { String print(T object, Locale locale); }
import java.text.ParseException; public interface Parser<T> { T parse(String clientValue, Locale locale) throws ParseException; }
想要创建你自己的Formatter,只要实现Formatter接口即可。参数T是需要格式化的对象类型,比如java.util.Date。实现print()方法用来在客户本地打印出一个格式化且本地化了的T的实例。实现parse()方法来将一个客户端的格式化数据转化成一个T的实例。如果一个解析操作失败了,那么你的Formatter应该抛出一个ParseException或是IllegalArgumentException。确保你的Formatter实现是线程安全的。
为了方便起见,format子包中已经提供了一些Formatter的实现。number包提供了一个NumberFormatter,CurrencyFormatter和PercentFormatter,它们使用java.text.NumberFormat来格式化java.lang.Number对象。datetime包提供了一个DateFormatter,它使用java.text.DateFormat来格式化java.util.Date对象。datetime.joda包提供了一个基于Joda Time库的全面的datetime格式化的支持。
考虑一个Formatter实现类的例子——DateFormatter
package org.springframework.format.datetime; public final class DateFormatter implements Formatter<Date> { private String pattern; public DateFormatter(String pattern) { this.pattern = pattern; } public String print(Date date, Locale locale) { if(date == null) { return ""; } return getDateFormat(locale).format(date); } public Date parse(String formatted, Locale locale) throws ParseException { if(formatted.length() == 0) { return null; } return getDateFormat(locale).parse(formatted); } protected DateFormat getDateFormat(Locale locale) { DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale); dateFormat.setLenient(false); return dateFormat; } }
Spring团队欢迎来自社区的对于Formatter的贡献。可以通过http://jira.springframework.org来投稿。
一会你将会看到的,字段格式化可以通过字段类型或注解配置。为了绑定一个注解和一个格式化,实现AnnotationFormatterFactory(译者注:将A和这个由A而使这个Factory产生的Printer和Parser关联起来):
package org.springframework.format; public interface AnnotationFormatterFactory<A extends Annotation> { Set<Class<?>> getFieldTypes(); Printer<?> getPrinter(A annotation, Class<?> fieldType); Parser<?> getParser(A annotation, Class<?> fieldType); }
参数A是要关联格式化逻辑的字段的注解类型,比如org.springframework.format.annotation.DateTimeFormat。getFieldTypes()方法来返回可能会被使用注解的字段类型。getPrinter()方法用来返回一个Printer来输出被注解的字段的值。而getParser()方法用来返回一个Parser来将一个被注解的字段解析成客户端要显示的值。
下面的AnnotationFormatterFactory将@NumberFormat注解绑定到了一个formatter。这个注解允许定义一个数字风格(style,译者注:比如CURRENCY、PERCENT)或是一个数字格式(pattern,译者注:比如#,###):
public final class NumberFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<NumberFormat> { public Set<Class<?>> getFieldTypes() { return new HashSet<Class<?>>(asList(newClass<?>[] { Short.class, Integer.class, Long.class, Float.class, Double.class, BigDecimal.class, BigInteger.class})); } public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) { return configureFormatterFrom(annotation, fieldType); } public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) { return configureFormatterFrom(annotation, fieldType); } private Formatter<Number> configureFormatterFrom( NumberFormat annotation, Class<?> fieldType) { if (!annotation.pattern().isEmpty()) { return new NumberFormatter(annotation.pattern()); } else { Style style = annotation.style(); if (style == Style.PERCENT) { return new PercentFormatter(); } else if (style == Style.CURRENCY) { return new CurrencyFormatter(); } else { return new NumberFormatter(); } } } }
为了触发格式化,只需要在字段上添加@NumberFormat注解即可:
public class MyModel { @NumberFormat(style=Style.CURRENCY) private BigDecimal decimal; }
org.springframework.format.annocation包中包含了一个便捷的格式化注解API。使用@NumberFormat来格式化java.lang.Number字段。使用@DateTimeFormat来格式化java.util.Date,java.util.Calendar,java.util.Long或Joda Time字段。
下面的例子使用@DateTimeFormat将java.util.Date格式化为ISO日期类型(yyyy-MM-dd):
public class MyModel { @DateTimeFormat(iso=ISO.DATE) private Date date; }
FormatterRegistry是一个注册formatters和converters的SPI。FormattingConversionService是FormatterRegistry的一个实现类,并且适用于大多数环境。这个实现类可以使用FormattingConversionServiceFactoryBean来编程式或声明式的配置成为一个Spring的Bean。由于这个实现也实现了ConversionService,所以它可以直接配置并和Spring的DataBinder和SpEL一起使用。
下面看一下FormatterRegistry SPI:
package org.springframework.format; public interface FormatterRegistry extends ConverterRegistry { void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser); void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter); void addFormatterForFieldType(Formatter<?> formatter); void addFormatterForAnnotation(AnnotationFormatterFactory<?, ?> factory); }
正如上面所示,Formatters可以通过字段类型或者注解来注册。
FormatterRegistry SPI允许你集中配置格式化规则,而不是在各个Controller中重复这样的配置。比如,你可能想要将所有的Date字段都用一定的方式来格式化,或者是具有某一注解的字段都也用一定的方式来格式化。通过一个共享的FormatterRegistry,一旦你定义了规则,那么它们将随时都可以应用到格式化中。
FormatterRegistrar是一个SPI,它用来通过FormatterRegistry来注册formatters和converters。
package org.springframework.format; public interface FormatterRegistrar { void registerFormatters(FormatterRegistry registry); }
当需要为指定的格式化类别注册多个相关的converters和fomatters,比如Date类型的格式化时,FormatterRegistrar非常有用。
在Spring MVC应用中,你可以精确地配置一个自定义的ConversionService实例并将它作为一个MVC命名空间中annotation-driven元素的属性。这个ConversionService可以在Controller的数据绑定时任何需要类型转换的地方使用。如果没有精确地配置ConversionService,那么SpringMVC将会自动注册一个默认的formatters和converters,可以转换一些常见的类型,比如说numbers和dates(译者注:这里也是需要配置上<mvc:annotation-driven>的)。
为了依赖默认的格式化规则,在你的Spring MVC配置XML文件中不要加上额外的配置。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <mvc:annotation-driven /> </beans>
通过这么一行的配置,为Numbers和Date类型格式化的默认formatters就被注册好了。包括对@NumberFormat和@DateTimeFormat注解的支持。如果classpath下有Joda Time的jar包的话,那么还能提供对Joda Time格式化库的支持。
为了注入一个包含自定义formatters和converters的ConversionSerivce实例,我们需要设置conversion-service属性(mvc命名空间的annotation-driven中),然后在conversionService实例中指定自定义的converters,formatters或者FormatterRegistrars作为FormattingConversionServiceFactoryBean的属性:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"> <mvc:annotation-driven conversion-service="conversionService" /> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="org.example.MyConverter" /> </set> </property> <property name="formatters"> <set> <bean class="org.example.MyFormatter" /> <bean class="org.example.MyAnnotationFormatterFactory" /> </set> </property> <property name="formatterRegistrars"> <set> <bean class="org.example.MyFormatterRegistrar" /> </set> </property> </bean> </beans>
注意:当你要使用FormatterRegistrars时,可以阅读“FormatterRegistrarSPI”这一节得到更多关于FormattingConversionServiceFactoryBean的信息。