3.5Spring字段格式化

如前一节所述,core.convert是一个通用类型转换系统。它提供了一个统一的ConversionServiceAPI以及一个强类型转换器SPI,用于实现从一种类型到另一种类型的转换逻辑。Spring容器使用此系统绑定bean属性值。此外,Spring表达式语言(SPEL)和DataBinder都使用此系统绑定字段值。例如,当SPEL需要将short强制为long以完成expression.setValue(object bean,object value)尝试时,core.convert系统将执行强制。

现在考虑典型客户机环境(如Web或桌面应用程序)的类型转换要求。在这种环境中,你通常从字符串转换为支持客户端回发过程,以及从字符串转换为支持视图呈现过程。此外,你通常需要本地化字符串值。更通用的core.convert转换器SPI不能直接满足这种格式要求。为了直接解决这些问题,Spring3引入了一个方便的格式化程序SPI,它为客户机环境提供了一个简单而健壮的PropertyEditor实现替代方案。

通常,当需要实现通用类型转换逻辑时,可以使用Converter SPI,例如,在java.util.Date和Long之间转换。当你在客户端环境(如Web应用程序)中工作并且需要分析和打印本地化字段值时,可以使用Formatter SPI。ConversionService为两个SPI提供统一的类型转换API。

3.5.1 Formatter SPI

用于实现字段格式化逻辑的Formatter SPI是简单且强类型的。以下列表显示Formatter接口定义:

package org.springframework.format;
public interface Formatter<T> extends Printer<T>, Parser<T> {
}

Formatter继承了Printer和Parser接口,下面是这两个接口的定义:

public interface Printer<T> {
String print(T fieldValue, 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的实例。如果解析尝试失败,格式化程序应引发ParseException或IllegalArgumentException。注意确保格式化程序实现是线程安全的。

format子包提供了方便使用的Formatter几种实现,number包提供了NumberStyleFormatter, CurrencyStyleFormatter, 和 PercentStyleFormatter 使用java.text.NumberFormat来格式化Number对象。datetime包提供了DateFormatter使用java.text.DateFormat来格式化java.util.Dat对象。datetime.joda包基于joda时间库提供全面的datetime格式支持。

下面的DateFormatter是Formatter的一个实现:

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程序贡献。

3.5.2 注解驱动的Formatting

字段的格式化可以通过字段类型的配置或者通过注解来实现。要绑定一个注解到Formatter,可以实现AnnotationFormatterFactory接口。下面显示了AnnotationFormatterFactory接口的定义:

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为要与格式逻辑关联的字段的annotationType,例如org.springframework.format.annotation.DateTimeFormat。getFieldTypes()返回可在其上使用注解的字段类型。 getprinter()返回Printer以打印注解字段的值。getParser()返回一个Parser来解析注解字段的clientValue。

以下示例AnnotationFormatterFactory实现将@NumberFormat注解绑定到formatter程序,以指定数字样式或模式:

public final class NumberFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<NumberFormat> {
public Set<Class<?>> getFieldTypes() {
return new HashSet<Class<?>>(asList(new Class<?>[] {
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 NumberStyleFormatter(annotation.pattern());
} else {
Style style = annotation.style();
if (style == Style.PERCENT) {
return new PercentStyleFormatter();
} else if (style == Style.CURRENCY) {
return new CurrencyStyleFormatter();
} else {
return new NumberStyleFormatter();
}
}
}
}

可以使用@NumberFormat来触发格式化,如下所示:

public class MyModel {
@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}

Format注解API

org.springframework.format.annotation包中存在可移植format annotation API。你可以使用@NumberFormat来格式化Number字段,如Double和Long,使用@DateTimeFormat来格式化java.util.Date、java.util.Calendar, Long(毫秒时间戳)以及jsr-310 java.time和Joda-Time值类型。

以下示例使用@DateTimeFormat 将java.util.date格式化为ISO日期(yyyy-mm-dd):

public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}

3.5.3 FormatterRegistry SPI

FormatterRegistry是用于注册格式化程序和转换器的SPI。FormattingConversionService是一种适用于大多数环境的FormatTerregistry的实现。你可以通过编程或声明方式将此变量配置为SpringBean,例如,通过使用FormattingConversionServiceFactoryBean。因为这个实现也实现了ConversionService,所以可以直接配置它与Spring的DataBinder和Spring表达式语言(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允许你集中配置格式化规则,而不是在控制器之间复制这样的配置。例如,你可能希望强制所有日期字段以某种方式格式化,或者具有特定注解的字段以某种方式格式化。使用共享FormatterRegistry,你只需定义一次这些规则,这些规则在需要格式化时会自动使用。

3.5.4 FormatterRegistrar SPI

FormatterRegistrar是一个SPI,用于通过FormatterRegistry注册formatters和converters。下面的列表显示了它的接口定义:

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

在为给定格式类别(如日期格式)注册多个相关转换器和格式化程序时,FormatterRegistrar非常有用。如果声明性registration不足-例如,当formatter 需要在与其自身不同的特定字段类型下索引,或者在注册Printer/Parser对时,它也很有用。下一节提供有关converter和formatter注册的更多信息。

3.5.5.在 Spring MVC 中配置Formatting

参考 Spring MVC 的Conversion and Formatting 章节。