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.convert
Converter
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
物件,以及 DurationFormatter
,以 @DurationFormat.Style
列舉中定義的不同樣式格式化 Duration
物件 (請參閱格式註解 API)。
以下 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
可移植的格式註解 API 存在於 org.springframework.format.annotation
套件中。您可以使用 @NumberFormat
格式化 Number
欄位,例如 Double
和 Long
、@DurationFormat
以 ISO-8601 和簡化樣式格式化 Duration
欄位,以及 @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
)
如需更多詳細資訊,請參閱 @DateTimeFormat
、@DurationFormat
和 @NumberFormat
的 javadoc。
基於樣式的格式化和剖析依賴於地區設定敏感的模式,這些模式可能會根據 Java 執行階段而變更。具體而言,依賴於日期、時間或數字剖析和格式化的應用程式,在 JDK 20 或更高版本上執行時,可能會遇到不相容的行為變更。 使用 ISO 標準化格式或您控制的具體模式,可以可靠地進行獨立於系統且獨立於地區設定的日期、時間和數字值剖析和格式化。 對於 如需更多詳細資訊,請參閱 Spring Framework Wiki 中的 Date and Time Formatting with JDK 20 and higher 頁面。 |
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 章節中的 轉換和格式化。