# 3.5Spring字段格式化

如前一节所述，core.convert是一个通用类型转换系统。它提供了一个统一的ConversionServiceAPI以及一个强类型转换器SPI，用于实现从一种类型到另一种类型的转换逻辑。Spring容器使用此系统绑定bean属性值。此外，Spring表达式语言（SPEL）和DataBinder都使用此系统绑定字段值。例如，当SPEL需要将short强制为long以完成expression.setValue（object bean，object value）尝试时，core.convert系统将执行强制。

现在考虑典型客户机环境（如Web或桌面应用程序）的类型转换要求。在这种环境中，你通常从字符串转换为支持客户端回发过程，以及从字符串转换为支持视图呈现过程。此外，你通常需要本地化字符串值。更通用的core.convert转换器SPI不能直接满足这种格式要求。为了直接解决这些问题，Spring3引入了一个方便的格式化程序SPI，它为客户机环境提供了一个简单而健壮的PropertyEditor实现替代方案。

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

## 3.5.1 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的实例。如果解析尝试失败，格式化程序应引发ParseException或IllegalArgumentException。注意确保格式化程序实现是线程安全的。

format子包提供了方便使用的Formatter几种实现，number包提供了NumberStyleFormatter, CurrencyStyleFormatter, 和 PercentStyleFormatter 使用java.text.NumberFormat来格式化Number对象。datetime包提供了DateFormatter使用java.text.DateFormat来格式化java.util.Dat对象。datetime.joda包基于joda时间库提供全面的datetime格式支持。

下面的DateFormatter是Formatter的一个实现：

```
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程序贡献。

## 3.5.2 注解驱动的Formatting

字段的格式化可以通过字段类型的配置或者通过注解来实现。要绑定一个注解到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注解绑定到formatter程序，以指定数字样式或模式：

```
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来触发格式化，如下所示：

```
public class MyModel {

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

**Format注解API**

org.springframework.format.annotation包中存在可移植format annotation API。你可以使用@NumberFormat来格式化Number字段，如Double和Long，使用@DateTimeFormat来格式化java.util.Date、java.util.Calendar, Long（毫秒时间戳）以及jsr-310 java.time和Joda-Time值类型。

以下示例使用@DateTimeFormat 将java.util.date格式化为ISO日期（yyyy-mm-dd）：

```
public class MyModel {

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

## 3.5.3 FormatterRegistry SPI

FormatterRegistry是用于注册格式化程序和转换器的SPI。FormattingConversionService是一种适用于大多数环境的FormatTerregistry的实现。你可以通过编程或声明方式将此变量配置为SpringBean，例如，通过使用FormattingConversionServiceFactoryBean。因为这个实现也实现了ConversionService，所以可以直接配置它与Spring的DataBinder和Spring表达式语言（SPEL）一起使用。

下面的列表显示了FormatterRegistry SPI：

```
package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

    void addFormatterForFieldType(Formatter<?> formatter);

    void addFormatterForAnnotation(AnnotationFormatterFactory<?, ?> factory);
}
```

如上面所示，你可以按字段类型或注解注册formatters。

FormatterRegistry SPI允许你集中配置格式化规则，而不是在控制器之间复制这样的配置。例如，你可能希望强制所有日期字段以某种方式格式化，或者具有特定注解的字段以某种方式格式化。使用共享FormatterRegistry，你只需定义一次这些规则，这些规则在需要格式化时会自动使用。

## 3.5.4 FormatterRegistrar SPI

FormatterRegistrar是一个SPI，用于通过FormatterRegistry注册formatters和converters。下面的列表显示了它的接口定义：

```
package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);
}
```

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

## 3.5.5.在 Spring MVC 中配置Formatting

参考 Spring MVC 的Conversion and Formatting 章节。
