|
对于最新稳定版本,请使用 Spring Framework 7.0.6! |
Spring 字段格式化
正如上一节所讨论的,core.convert 是一个通用的类型转换系统。它提供了一个统一的 ConversionService API,以及一个强类型的 Converter SPI,用于实现从一种类型到另一种类型的转换逻辑。Spring 容器使用该系统来绑定 Bean 属性值。此外,Spring 表达式语言 (SpEL) 和 DataBinder 也使用该系统来绑定字段值。例如,当 SpEL 需要将 Short 强制转换为 Long 以完成 expression.setValue(Object bean, Object value) 尝试时,core.convert 系统会执行该强制转换。
现在考虑典型客户端环境(例如 Web 应用程序或桌面应用程序)中的类型转换需求。在这些环境中,通常需要将 String 转换为其他类型以支持客户端的回传(postback)过程,同时也需要将其他类型转换回 String 以支持视图渲染过程。此外,通常还需要对 String 值进行本地化处理。core.convert 中更通用的 Converter SPI 并未直接解决此类格式化需求。为了直接应对这些需求,Spring 提供了一个便捷的 Formatter SPI,它为客户环境提供了一种简单而健壮的替代方案,用以取代 PropertyEditor 的实现。
通常,当你需要实现通用的类型转换逻辑时(例如,在 Converter 和 java.util.Date 之间进行转换),可以使用 Long SPI。
当你在客户端环境(例如 Web 应用程序)中工作,并且需要解析和打印本地化的字段值时,可以使用 Formatter SPI。
ConversionService 为这两个 SPI 提供了统一的类型转换 API。
这FormatterSPI
用于实现字段格式化逻辑的 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的实例。如果解析尝试失败,您的Formatter
应抛出ParseException或IllegalArgumentException。请务必确保您的Formatter实现是线程安全的。
format 子包为方便起见提供了多种 Formatter 实现。
number 包提供了 NumberStyleFormatter、CurrencyStyleFormatter 和
PercentStyleFormatter,用于格式化使用 java.text.NumberFormat 的 Number 对象。
datetime 包提供了一个 DateFormatter,用于通过
java.text.DateFormat 格式化 java.util.Date 对象。
以下的 DateFormatter 是一个 Formatter 实现示例:
-
Java
-
Kotlin
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;
}
}
class DateFormatter(private val pattern: String) : Formatter<Date> {
override fun print(date: Date, locale: Locale)
= getDateFormat(locale).format(date)
@Throws(ParseException::class)
override fun parse(formatted: String, locale: Locale)
= getDateFormat(locale).parse(formatted)
protected fun getDateFormat(locale: Locale): DateFormat {
val dateFormat = SimpleDateFormat(this.pattern, locale)
dateFormat.isLenient = false
return dateFormat
}
}
Spring 团队欢迎社区驱动的 Formatter 贡献。请参阅GitHub Issues以参与贡献。
注解驱动的格式化
字段格式化可以通过字段类型或注解进行配置。要将一个注解绑定到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 注解绑定到一个格式化器,以允许指定数字的样式或模式:
-
Java
-
Kotlin
public final class NumberFormatAnnotationFormatterFactory
implements AnnotationFormatterFactory<NumberFormat> {
private static final Set<Class<?>> FIELD_TYPES = Set.of(Short.class,
Integer.class, Long.class, Float.class, Double.class,
BigDecimal.class, BigInteger.class);
public Set<Class<?>> getFieldTypes() {
return FIELD_TYPES;
}
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
return switch(annotation.style()) {
case Style.PERCENT -> new PercentStyleFormatter();
case Style.CURRENCY -> new CurrencyStyleFormatter();
default -> new NumberStyleFormatter();
};
}
}
class NumberFormatAnnotationFormatterFactory : AnnotationFormatterFactory<NumberFormat> {
override fun getFieldTypes(): Set<Class<*>> {
return setOf(Short::class.java, Int::class.java, Long::class.java, Float::class.java, Double::class.java, BigDecimal::class.java, BigInteger::class.java)
}
override fun getPrinter(annotation: NumberFormat, fieldType: Class<*>): Printer<Number> {
return configureFormatterFrom(annotation, fieldType)
}
override fun getParser(annotation: NumberFormat, fieldType: Class<*>): Parser<Number> {
return configureFormatterFrom(annotation, fieldType)
}
private fun configureFormatterFrom(annotation: NumberFormat, fieldType: Class<*>): Formatter<Number> {
return if (annotation.pattern.isNotEmpty()) {
NumberStyleFormatter(annotation.pattern)
} else {
val style = annotation.style
when {
style === NumberFormat.Style.PERCENT -> PercentStyleFormatter()
style === NumberFormat.Style.CURRENCY -> CurrencyStyleFormatter()
else -> NumberStyleFormatter()
}
}
}
}
要触发格式化,您可以使用 @NumberFormat 注解字段,如下例所示:
-
Java
-
Kotlin
public class MyModel {
@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}
class MyModel(
@field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)
格式化注解 API
在 org.springframework.format.annotation 包中存在一个可移植的格式注解 API。您可以使用 @NumberFormat 来格式化 Number 字段(例如 Double 和 Long),并使用 @DateTimeFormat 来格式化 java.util.Date、java.util.Calendar、Long(用于毫秒时间戳)以及 JSR-310 的 java.time。
以下示例使用 @DateTimeFormat 将 java.util.Date 格式化为 ISO 日期
(yyyy-MM-dd):
-
Java
-
Kotlin
public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}
class MyModel(
@DateTimeFormat(iso=ISO.DATE) private val date: Date
)
这FormatterRegistrySPI
FormatterRegistry 是一个用于注册格式化器(formatter)和转换器(converter)的 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,您只需定义一次这些规则,它们就会在需要格式化时自动应用。
这FormatterRegistrarSPI
FormatterRegistrar 是一个 SPI(服务提供者接口),用于通过 FormatterRegistry 注册格式化器(formatter)和转换器(converter)。以下代码清单展示了其接口定义:
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
FormatterRegistrar 在为某一格式化类别(例如日期格式化)注册多个相关的转换器(converter)和格式化器(formatter)时非常有用。当声明式注册方式不够用时,它也很有帮助——例如,当某个格式化器需要根据与其自身 <T> 类型不同的特定字段类型进行索引,或者在注册 Printer/Parser 对时。下一节将提供更多关于转换器和格式化器注册的信息。
在 Spring MVC 中配置格式化
参见 Spring MVC 章节中的转换与格式化。