6.6 Spring3 字段格式化

6.6Spring3 字段格式化

正如在前面的小节中说的那样,core.convert是一个通用的类型转换系统。它提供了一个统一的ConversionServiceAPI和一个强类型的Converter SPI来实现类型转换逻辑。Spring容器使用这个系统来绑定bean的属性值。另外,SpELDataBinder也都利用这个系统来绑定字段值。比如,当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.Datejava.lang.Long类型之间互相转换时。而当你处于客户端环境时,比如在web应用中,需要将字段值解析并以本地化格式输出时使用FormatterSPIConversionService为两种SPI提供了一个统一的类型转换API


FormatterSPI

实现了字段格式化逻辑的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;
	}

格式化注解API

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 SPI

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

FormatterRegistrar是一个SPI,它用来通过FormatterRegistry来注册formatters和converters。

	package org.springframework.format;
	
	public interface FormatterRegistrar {
		
		void registerFormatters(FormatterRegistry registry);
	}

当需要为指定的格式化类别注册多个相关的converters和fomatters,比如Date类型的格式化时,FormatterRegistrar非常有用。


在Spring MVC中配置Formatting

在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的信息。









你可能感兴趣的:(6.6 Spring3 字段格式化)