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
对象。
以下是一个示例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
)
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章节中的转换和格式化。