正如上一节所讨论的,core.convert
是一个通用的类型转换系统。它提供了一个统一的 ConversionService API,以及一个强类型的 Converter SPI,用于实现从一种类型到另一种类型的转换逻辑。Spring 容器使用这个系统来绑定 Bean 的属性值。此外,Spring 表达式语言(SpEL)和 DataBinder 都使用这个系统来绑定字段值。例如,当 SpEL 需要将 Short 强制转换为 Long 以完成expression.setValue(Object bean, Object value)
的尝试时,core.convert
系统会执行强制转换的操作。
现在考虑一下典型的客户端环境的类型转换要求,比如说网络或桌面应用程序。在这样的环境中,你通常要从 String 转换,以支持客户端的 postback 过程,以及回到 String 以支持视图的渲染过程。此外,你经常需要对 String 值进行本地化。更加通用的 core.convert
Converter SPI
并没有直接解决这种格式化要求。为了直接解决这些问题,Spring 3 引入了一个方便的 Formatter SPI ,为客户端环境提供了 PropertyEditor 实现的简单而强大的替代方案。
一般来说,当你需要实现通用的类型转换逻辑时,你可以使用 Converter SPI,例如,在 java.util.Date
和 Long 之间进行转换。当你在客户端环境中工作(如 Web 应用程序)并需要解析和打印本地化的字段值时,你可以使用 Formatter SPI
。ConversionService 为这两个 SPI 提供了一个统一的类型转换 API。
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;
}
要创建你自己的格式化器,实现前面所示的格式化器接口。将 T 参数化为你希望格式化的对象的类型—例如,java.util.Date
。实现 print()
操作,打印一个 T 的实例,以便在客户机上显示。实现parse()
操作,从客户端 locale 返回的格式化表示中解析 T 的一个实例。如果解析尝试失败,你的格式化器应该抛出一个 ParseException 或 IllegalArgumentException。请注意确保你的格式化器的实现是线程安全的。
format
子包提供了几个 Formatter 的实现,作为一种方便。Number 包提供了NumberStyleFormatter、CurrencyStyleFormatter 和 PercentStyleFormatter,用于格式化使用java.text.NumberFormat
的 Number 对象。datetime 包提供了一个 DateFormatter 来格式化使用java.text.DateFormat
的 java.util.Date
对象。
下面的 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 贡献。请参阅 GitHub Issues 来贡献。
注解驱动的格式化
字段格式化可以通过字段类型或注解来配置。为了将注解与格式化器绑定,需要实现 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 参数化为你希望与之关联格式化逻辑的字段注解类型—例如
org.springframework.format.annotation.DateTimeFormat
。 - 让
getFieldTypes()
返回注解可以使用的字段类型。 - 让
getPrinter()
返回一个 Printer 来打印一个注解字段的值。 - 让
getParser()
返回一个 Parser 来解析一个注解字段的 clientValue。
下面的例子 AnnotationFormatterFactory 的实现将 @NumberFormat
注解绑定到一个格式化器上,让数字样式或模式被指定:
public final class NumberFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<NumberFormat> {
/**
定义支持格式化的 class
*/
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 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;
}
格式化注解 API
org.springframework.format.annotation
包中存在一个可移植的格式注解 API。你可以使用@NumberFormat
来格式化 Double 和 Long 等数字字段,使用 @DateTimeFormat
来格式化java.util.Date
、java.util.Calendar
、Long(用于毫秒级时间戳)以及 JSR-310 java.time
。
下面的例子使用 @DateTimeFormat
将一个 java.util.Date
格式化为 ISO 日期(yyyy-MM-dd)。
public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}
FormatterRegistry SPI
FormatterRegistry 是一个用于注册格式化器和转换器的 SPI。FormattingConversionService 是FormatterRegistry 的一个实现,适用于大多数环境。你可以通过编程或声明的方式将这个变体配置为Spring Bean,例如,通过使用 FormattingConversionServiceFactoryBean。因为这个实现也实现了ConversionService,所以你可以直接配置它与 Spring 的 DataBinder 和 Spring表达式语言(SpEL)一起使用。
下面的列表显示了 FormatterRegistry SPI:
package org.springframework.format;
public interface FormatterRegistry extends ConverterRegistry {
void addPrinter(Printer<?> printer);
void addParser(Parser<?> parser);
void addFormatter(Formatter<?> formatter);
void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);
void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);
void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);
}
如前面的列表所示,你可以通过字段类型或注解来注册格式化器。
FormatterRegistry SPI 让你集中配置格式化规则,而不是在你的控制器中重复这种配置。例如,你可能想强制要求所有的日期字段以某种方式格式化,或者要求具有特定注解的字段以某种方式格式化。通过一个共享的 FormatterRegistry,你只需定义一次这些规则,并且在需要格式化的时候就可以应用这些规则。
FormatterRegistrar SPI
FormatterRegistrar 是一个 SPI,用于通过 FormatterRegistry 来注册格式化器和转换器。下面的列表显示了它的接口定义:
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
当为一个给定的格式化类别(如日期格式化)注册多个相关的转换器和格式器时,FormatterRegistrar 很有用。在声明式注册不充分的情况下,它也是有用的—例如,当一个格式化器需要被索引到不同于它自己的 <T>
的特定字段类型下,或者注册一个 Printer/Parser 对时。下一节将提供更多关于转换器和格式化器注册的信息。
在 Spring MVC 中配置格式化
参见 Spring MVC 章节中的转换和格式化。