|
对于最新的稳定版本,请使用 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进行转换以支持客户端回传过程,同时还需转换回String以支持视图渲染过程。此外,常常需要对String值进行本地化处理。更通用的core.convertConverter SPI并未直接解决此类格式化需求。为此,Spring提供了便捷的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;
}
要创建您自己的 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问题以进行贡献。
注解驱动的格式化
字段格式可以根据字段类型或注解进行配置。要将注解绑定到 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
存在一个可移植的格式注解API,位于org.springframework.format.annotation包中。您可以使用@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
)
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 是通过 FormatterRegistry 注册格式化程序和转换器的 SPI。以下列表显示了它的接口定义:
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
一个 FormatterRegistrar 在注册多个相关的转换器和格式化程序时很有用,这些转换器和格式化程序属于给定的格式类别,例如日期格式。当声明式注册不足时,它也可能有用 — 例如,当格式化程序需要根据不同于其自身 <T> 的特定字段类型进行索引,或者当注册一个 Printer/Parser 对时。下一节将提供更多关于转换器和格式化程序注册的信息。
在Spring MVC中配置格式化
查看Spring MVC章节中的转换和格式化。