Spring Field Formatting
正如前一节中所讨论的,core.convert
是一个通用的类型转换系统。它提供了一个统一的`ConversionService`API 以及一个强类型`Converter`SPI,用于实现从一种类型到另一种类型的转换逻辑。Spring 容器使用此系统绑定 bean 属性值。此外,Spring 表达式语言 (SpEL) 和`DataBinder`都使用此系统绑定字段值。例如,当 SpEL 需要将`Short`强制转换为`Long`以完成`expression.setValue(Object bean, Object value)`尝试时,`core.convert`系统执行强制转换。
As discussed in the previous section, core.convert
is a
general-purpose type conversion system. It provides a unified ConversionService
API as
well as a strongly typed Converter
SPI for implementing conversion logic from one type
to another. A Spring container uses this system to bind bean property values. In
addition, both the Spring Expression Language (SpEL) and DataBinder
use this system to
bind field values. For example, when SpEL needs to coerce a Short
to a Long
to
complete an expression.setValue(Object bean, Object value)
attempt, the core.convert
system performs the coercion.
现在,考虑一个典型客户端环境(例如 Web 或桌面应用程序)的类型转换要求。在这种环境中,您通常从 String 转换以支持客户端回发过程,然后再转换回 String 以支持视图渲染过程。此外,您通常需要本地化 String 值。更通用的 core.convert Converter SPI 不会直接解决此类格式化要求。为了直接解决这些要求,Spring 提供了一个方便的 Formatter SPI,它为客户端环境中的 PropertyEditor 实现提供了一个简单且可靠的替代方案。
Now consider the type conversion requirements of a typical client environment, such as a
web or desktop application. In such environments, you typically convert from String
to support the client postback process, as well as back to String
to support the
view rendering process. In addition, you often need to localize String
values. The more
general core.convert
Converter
SPI does not address such formatting requirements
directly. To directly address them, Spring provides a convenient Formatter
SPI that
provides a simple and robust alternative to PropertyEditor
implementations for client
environments.
通常,当您需要实现通用类型转换逻辑(例如,在 java.util.Date 与 Long 之间进行转换)时可以使用 Converter SPI。当您在客户端环境(例如 Web 应用程序)中工作并且需要解析和打印本地化字段值时可以使用 Formatter SPI。ConversionService 为这两个 SPI 提供了统一的类型转换 API。
In general, you can use the Converter
SPI when you need to implement general-purpose type
conversion logic — for example, for converting between a java.util.Date
and a Long
.
You can use the Formatter
SPI when you work in a client environment (such as a web
application) and need to parse and print localized field values. The ConversionService
provides a unified type conversion API for both SPIs.
The Formatter
SPI
实现字段格式化逻辑的 Formatter SPI 简单且类型强。以下清单显示了 Formatter 接口定义:
The Formatter
SPI to implement field formatting logic is simple and strongly typed. The
following listing shows the Formatter
interface definition:
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
Formatter
扩展自 Printer
和 Parser
构建块接口。以下清单显示了这两个接口的定义:
Formatter
extends from the Printer
and Parser
building-block interfaces. The
following listing shows the definitions of those two interfaces:
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 实现是线程安全的。
To create your own Formatter
, implement the Formatter
interface shown earlier.
Parameterize T
to be the type of object you wish to format — for example,
java.util.Date
. Implement the print()
operation to print an instance of T
for
display in the client locale. Implement the parse()
operation to parse an instance of
T
from the formatted representation returned from the client locale. Your Formatter
should throw a ParseException
or an IllegalArgumentException
if a parse attempt fails. Take
care to ensure that your Formatter
implementation is thread-safe.
format
子包提供了几个 Formatter 实现供您方便使用。number
包提供了 NumberStyleFormatter
、CurrencyStyleFormatter
和 PercentStyleFormatter
来格式化使用 java.text.NumberFormat 的 Number 对象。datetime
包提供 DateFormatter
来使用 java.text.DateFormat
格式化 java.util.Date
对象。
The format
subpackages provide several Formatter
implementations as a convenience.
The number
package provides NumberStyleFormatter
, CurrencyStyleFormatter
, and
PercentStyleFormatter
to format Number
objects that use a java.text.NumberFormat
.
The datetime
package provides a DateFormatter
to format java.util.Date
objects with
a java.text.DateFormat
.
以下 DateFormatter 是一个示例 Formatter 实现:
The following DateFormatter
is an example Formatter
implementation:
-
Java
-
Kotlin
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`贡献。请参阅https://github.com/spring-projects/spring-framework/issues[GitHub Issues]进行贡献。
The Spring team welcomes community-driven Formatter
contributions. See
GitHub Issues to contribute.
Annotation-driven Formatting
可以通过字段类型或注释来配置字段格式化。要将注释绑定到 Formatter,请实现 AnnotationFormatterFactory
。以下清单显示了 AnnotationFormatterFactory
接口的定义:
Field formatting can be configured by field type or annotation. To bind
an annotation to a Formatter
, implement AnnotationFormatterFactory
. The following
listing shows the definition of the AnnotationFormatterFactory
interface:
public interface AnnotationFormatterFactory<A extends Annotation> {
Set<Class<?>> getFieldTypes();
Printer<?> getPrinter(A annotation, Class<?> fieldType);
Parser<?> getParser(A annotation, Class<?> fieldType);
}
要创建一个实现:
To create an implementation:
-
Parameterize
A
to be the fieldannotationType
with which you wish to associate formatting logic — for exampleorg.springframework.format.annotation.DateTimeFormat
. -
Have
getFieldTypes()
return the types of fields on which the annotation can be used. -
Have
getPrinter()
return aPrinter
to print the value of an annotated field. -
Have
getParser()
return aParser
to parse aclientValue
for an annotated field.
以下示例 AnnotationFormatterFactory 实现将 @NumberFormat
注释绑定到一个格式化器,以便指定数字样式或模式:
The following example AnnotationFormatterFactory
implementation binds the @NumberFormat
annotation to a formatter to let a number style or pattern be specified:
-
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
为字段添加注释,如下面的示例所示:
To trigger formatting, you can annotate fields with @NumberFormat
, as the following
example shows:
-
Java
-
Kotlin
public class MyModel {
@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}
class MyModel(
@field:NumberFormat(style = Style.CURRENCY) private val decimal: BigDecimal
)
Format Annotation API
一个可移植格式注释 API 存在于 org.springframework.format.annotation
包中。您可以使用 @NumberFormat
格式化数字字段(例如 Double 和 Long),使用 @DateTimeFormat
格式化 java.util.Date
、java.util.Calendar
、Long
(用于毫秒时间戳)以及 JSR-310 java.time
。
A portable format annotation API exists in the org.springframework.format.annotation
package. You can use @NumberFormat
to format Number
fields such as Double
and
Long
, and @DateTimeFormat
to format java.util.Date
, java.util.Calendar
, Long
(for millisecond timestamps) as well as JSR-310 java.time
.
以下示例使用 @DateTimeFormat
将 java.util.Date
格式化为一个 ISO 日期(yyyy-MM-dd):
The following example uses @DateTimeFormat
to format a java.util.Date
as an ISO Date
(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
)
The FormatterRegistry
SPI
FormatterRegistry
是一种用于注册格式化器和转换器的 SPI。FormattingConversionService
是 FormatterRegistry
的一个实现,适合大多数环境。您可以以编程方式或声明方式配置此变量作为一个 Spring Bean,例如,通过使用 FormattingConversionServiceFactoryBean
。由于此实现还实现了 ConversionService
,您可以直接对其进行配置,以与 Spring 的 DataBinder
和 Spring 表达式语言 (SpEL) 配合使用。
The FormatterRegistry
is an SPI for registering formatters and converters.
FormattingConversionService
is an implementation of FormatterRegistry
suitable for
most environments. You can programmatically or declaratively configure this variant
as a Spring bean, e.g. by using FormattingConversionServiceFactoryBean
. Because this
implementation also implements ConversionService
, you can directly configure it
for use with Spring’s DataBinder
and the Spring Expression Language (SpEL).
以下清单显示了 FormatterRegistry
SPI:
The following listing shows the FormatterRegistry
SPI:
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);
}
如前一个清单所示,您可以按字段类型或注释注册格式化器。
As shown in the preceding listing, you can register formatters by field type or by annotation.
FormatterRegistry
SPI 允许您集中配置格式化规则,而不是在控制器中重复此类配置。例如,您可能希望强制所有日期字段以某种方式格式化,或以某种方式格式化具有特定注释的字段。借助一个共享 FormatterRegistry
,您只需定义一次这些规则,然后在需要格式化时应用它们。
The FormatterRegistry
SPI lets you configure formatting rules centrally, instead of
duplicating such configuration across your controllers. For example, you might want to
enforce that all date fields are formatted a certain way or that fields with a specific
annotation are formatted in a certain way. With a shared FormatterRegistry
, you define
these rules once, and they are applied whenever formatting is needed.
The FormatterRegistrar
SPI
FormatterRegistrar
是一种通过 FormatterRegistry 注册格式化器和转换器的 SPI。以下清单显示了它的接口定义:
FormatterRegistrar
is an SPI for registering formatters and converters through the
FormatterRegistry. The following listing shows its interface definition:
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
当为给定的格式化类别(例如日期格式化)注册多个相关的转换器和格式器时,FormatterRegistrar
非常有用。在声明注册不足时,它也可能非常有用,例如,当格式器需要在不同于其自己的 <T>
的特定字段类型下进行索引或注册 Printer
/Parser
对时。下一节提供了有关转换器和格式器注册的更多信息。
A FormatterRegistrar
is useful when registering multiple related converters and
formatters for a given formatting category, such as date formatting. It can also be
useful where declarative registration is insufficient — for example, when a formatter
needs to be indexed under a specific field type different from its own <T>
or when
registering a Printer
/Parser
pair. The next section provides more information on
converter and formatter registration.
Configuring Formatting in Spring MVC
请参阅 Spring MVC 章节中的Conversion and Formatting。
See Conversion and Formatting in the Spring MVC chapter.