# 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)。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.shiker.tech/spring-he-xin-gong-neng/3.-yan-zheng-shu-ju-bang-ding-he-lei-xing-zhuan-huan/3.5.-spring-zi-duan-ge-shi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
