跳到主要内容
版本:7.0.3

Spring字段格式化

Hunyuan 7b 中英对照 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系统会执行这种强制转换。

现在考虑一个典型客户端环境(如Web或桌面应用程序)的类型转换需求。在这样的环境中,通常需要将数据从String类型转换过来以支持客户端的回发过程,同时还需要将其转换回String类型以支持视图渲染过程。此外,还经常需要对String值进行本地化处理。更通用的core.convert Converter SPI并不能直接满足这些格式化需求。为了直接解决这些问题,Spring提供了便捷的Formatter SPI,它为客户端环境提供了一种简单而强大的替代方案,可以替代PropertyEditor的实现。

一般来说,当你需要实现通用类型转换逻辑时(例如,在 java.util.DateLong 之间进行转换),可以使用 Converter SPI。当你在客户端环境中工作(如 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 继承自 PrinterParser 这两个基础接口。以下列出了这两个接口的定义:

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 应该抛出 ParseExceptionIllegalArgumentException。请确保您的 Formatter 实现是线程安全的。

format 子包提供了几种 Formatter 实现,以方便使用。number 包提供了 NumberStyleFormatterCurrencyStyleFormatterPercentStyleFormatter,用于格式化使用 java.text.NumberFormatNumber 对象。datetime 包提供了一个 DateFormatter,用于使用 java.text.DateFormat 格式化 java.util.Date 对象;同时还有一个 DurationFormatter,可以按照 @DurationFormat.Style 枚举中定义的不同样式来格式化 Duration 对象(详见 Format Annotation API)。

以下是DateFormatter的一个示例实现:

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;
}
}

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);
}

要创建一个实现:

  1. A 参数化为您希望关联格式化逻辑的字段 annotationType —— 例如 org.springframework.format.annotation.DateTimeFormat

  2. 使 getFieldTypes() 返回可以应用该注解的字段类型。

  3. 使 getPrinter() 返回一个 Printer,用于打印带有注解的字段的值。

  4. 使 getParser() 返回一个 Parser,用于解析带有注解的字段的 clientValue

以下示例中的AnnotationFormatterFactory实现将@NumberFormat注解与一个格式化器(formatter)绑定起来,以便指定数字的样式或格式:

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();
};
}
}

要触发格式化,你可以像以下示例所示,使用@NumberFormat来标记字段:

public class MyModel {

@NumberFormat(style=Style.CURRENCY)
private BigDecimal decimal;
}

格式注释 API

org.springframework.format.annotation包中存在一个可移植的格式注解API。你可以使用@NumberFormat来格式化Number类型的字段,如DoubleLong;使用@DurationFormat来以ISO-8601标准和简化样式格式化Duration类型的字段;使用@DateTimeFormat来格式化java.util.Datejava.util.Calendar以及Long(用于毫秒时间戳)类型的字段,同时也可以格式化JSR-310中的java.time类型字段。

以下示例使用 @DateTimeFormatjava.util.Date 格式化为 ISO 日期(yyyy-MM-dd):

public class MyModel {

@DateTimeFormat(iso=ISO.DATE)
private Date date;
}

有关更多详细信息,请参阅@DateTimeFormat@DurationFormat@NumberFormat的javadoc。

注意

基于样式的格式化和解析依赖于区域设置敏感的模式,这些模式可能会根据Java运行时环境的不同而发生变化。具体来说,那些依赖于日期、时间或数字的解析和格式化的应用程序在JDK 20或更高版本上运行时,可能会遇到兼容性问题。

使用ISO标准化格式或由你控制的特定模式,可以实现对日期、时间和数字值的可靠、与系统无关且与区域设置无关的解析和格式化。

对于@DateTimeFormat,使用备用模式也有助于解决兼容性问题。

有关更多详细信息,请参阅Spring Framework wiki中的使用JDK 20及更高版本进行日期和时间格式化页面。

FormatterRegistry SPI

FormatterRegistry 是一个用于注册格式化器和转换器的 SPI(Service Provider Interface)。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(Service Provider Interface),用于通过 FormatterRegistry 注册格式化器(formatters)和转换器(converters)。以下列出了其接口定义:

package org.springframework.format;

public interface FormatterRegistrar {

void registerFormatters(FormatterRegistry registry);
}

当需要为给定的格式化类别(如日期格式化)注册多个相关的转换器和格式化器时,FormatterRegistrar非常有用。在声明式注册不足以满足需求的情况下,它也表现出其价值——例如,当某个格式化器需要根据与其自身类型 <T> 不同的特定字段类型进行索引时,或者在注册 Printer/Parser 对时。下一节将提供更多关于转换器和格式化器注册的信息。

在Spring MVC中配置格式化

请参阅Spring MVC章节中的转换和格式化