# 3.5. spring字段格式

正如上一节所讨论的，[`core.convert`](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#core-convert)是一个通用的类型转换系统。它提供了一个统一的`ConversionService`API 以及一个强类型的`Converter`SPI，用于实现从一种类型到另一种类型的转换逻辑。Spring 容器使用此系统绑定 bean 属性值。此外，Spring 表达式语言（SpEL）都使用这个系统来绑定字段值。例如，当 SpEL 需要将 `Short`强制转换为`Long`以完成一次`expression.setValue(Object bean, Object value)`尝试时，`core.convert` 系统会执行强制。

现在考虑典型客户端环境的类型转换要求，例如 Web 或桌面应用程序。在这样的环境中，您通常转换`String` 以支持客户端回发过程，以及转换回`String`以支持视图呈现过程。此外，您经常需要本地化`String`值。更通用的`core.convert` `Converter`SPI 不直接解决此类格式要求。为了直接解决这些问题，Spring 3 引入了一个方便的`Formatter`SPI，它为客户端环境的实现提供了一个简单而健壮的替代方案。`PropertyEditor`

`Converter`通常，当您需要实现通用类型转换逻辑时，您可以使用SPI——例如，在 a`java.util.Date`和 a之间进行转换`Long`。`Formatter`当您在客户端环境（例如 Web 应用程序）中工作并且需要解析和打印本地化的字段值时，您可以使用SPI。`ConversionService` 为两个 SPI 提供了统一的类型转换 API 。

**3.5.1. `Formatter`SPI**

实现字段格式化逻辑的`Formatter`SPI 简单且强类型。以下清单显示了`Formatter`接口定义：

```java
package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}
```

`Formatter`从`Printer`和`Parser`构建块接口进行扩展。以下清单显示了这两个接口的定义：

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

要创建您自己的格式化程序，请实现前面所示的格式化程序接口。将 `T` 参数化为您希望格式化的对象类型——例如 `java.util.Date`。实现 `print()` 操作以打印 `T` 的实例以在客户端区域设置中显示。实现 `parse()` 操作以从客户端语言环境返回的格式化表示中解析 `T` 的实例。如果解析尝试失败，您的 `Formatter`应该抛出 `ParseException` 或 `IllegalArgumentException`。请注意确保您的 Formatter 实现是线程安全的。

为了方便起见，格式子包提供了几种 `Formatter` 实现。 `number` 包提供 `NumberStyleFormatter`、`CurrencyStyleFormatter` 和`PercentStyleFormatter`来格式化使用 `java.text.NumberFormat` 的 `Number` 对象。`datetime`包提供了一个 `DateFormatter` 来使用`java.text.DateFormat`来格式化`java.util.Date`对象。

下面`DateFormatter`是一个实现`Formatter`的示例：

```java
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 问题](https://github.com/spring-projects/spring-framework/issues)以做出贡献。

**3.5.2. 注解驱动的格式化**

字段格式可以通过字段类型或注解进行配置。要将注解绑定到 `Formatter`，请实现`AnnotationFormatterFactory`. 以下清单显示了`AnnotationFormatterFactory`接口的定义：

```java
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. 将 `annotationType`参数化为您希望与格式化逻辑相关联的字段 - 例如`org.springframework.format.annotation.DateTimeFormat`.
2. `getFieldTypes()`已返回可以使用注解的字段类型。
3. `getPrinter()`返回`Printer`以打印带注解的字段的值。
4. 已`getParser()`返回 `Parser`以解析带`clientValue`注解的字段。

以下示例`AnnotationFormatterFactory`的实现将`@NumberFormat` 注解绑定到格式化程序以指定数字样式或模式：

```java
public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory<NumberFormat> {

    public Set<Class<?>> getFieldTypes() {
        return new HashSet<Class<?>>(asList(new Class<?>[] {
            Short.class, Integer.class, Long.class, Float.class,
            Double.class, BigDecimal.class, BigInteger.class }));
    }

    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 {
            Style style = annotation.style();
            if (style == Style.PERCENT) {
                return new PercentStyleFormatter();
            } else if (style == Style.CURRENCY) {
                return new CurrencyStyleFormatter();
            } else {
                return new NumberStyleFormatter();
            }
        }
    }
}
```

要触发格式化，您可以使用`@NumberFormat` 注解字段，如以下示例所示：

```java
public class MyModel {

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

**格式注解 API**

I `org.springframework.format.annotation`包中存在可移植格式注解 AP 。您可以使用`@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
public class MyModel {

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

**3.5.3. `FormatterRegistry`SPI**

`FormatterRegistry`是一个用于注册格式化程序和转换器的 SPI。 `FormattingConversionService`是`FormatterRegistry`适用于大多数环境的实现。您可以以编程方式或声明方式将此变体配置为 Spring bean，例如使用`FormattingConversionServiceFactoryBean`. 由于此实现还实现了`ConversionService` ，因此您可以直接将其配置为与 Spring`DataBinder`和 Spring 表达式语言 (SpEL) 一起使用。

以下清单显示了`FormatterRegistry`SPI：

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

如前面的清单所示，您可以按字段类型或注解注册格式化程序。

SPI 让您可以集中配置格式规则，而不是在`FormatterRegistry`控制器之间复制此类配置。例如，您可能希望强制所有日期字段都以某种方式格式化，或者具有特定注解的字段以某种方式格式化。使用共享的`FormatterRegistry`，您只需定义一次这些规则，并在需要格式化时应用它们。

**3.5.4. `FormatterRegistrar`SPI**

`FormatterRegistrar`是一个 SPI，用于通过 FormatterRegistry 注册格式化程序和转换器。下面的清单显示了它的接口定义：

```java
package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);
}
```

在为给定的格式类别（例如日期格式）注册多个相关转换器和格式器时，`FormatterRegistrar`很有用。在声明式注册不足的情况下，它也很有用——例如，当格式化程序需要在与其自身`<T>`不同的特定字段类型下进行索引时，或者在注册`Printer`/`Parser`对时。下一节提供有关转换器和格式化程序注册的更多信息。

**3.5.5. 在 Spring MVC 中配置格式化**

请参阅Spring MVC 章节中的[转换和格式化](https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-config-conversion)。
