Spring フィールドのフォーマット
前のセクションで説明したように、core.convert
は汎用の型変換システムです。統一された ConversionService
API と、ある型から別の型への変換ロジックを実装するための厳密に型指定された Converter
SPI を提供します。Spring コンテナーは、このシステムを使用して Bean プロパティ値をバインドします。さらに、Spring Expression Language(SpEL)と DataBinder
の両方がこのシステムを使用してフィールド値をバインドします。例: SpEL が expression.setValue(Object bean, Object value)
の試行を完了するために Short
を Long
に強制する必要がある場合、core.convert
システムは強制を実行します。
ここで、Web アプリケーションやデスクトップアプリケーションなどの一般的なクライアント環境の型変換要件について考えてみましょう。このような環境では、通常、クライアントポストバックプロセスをサポートするために String
から変換し、ビューレンダリングプロセスをサポートするために String
に戻します。さらに、多くの場合、String
値をローカライズする必要があります。より一般的な core.convert
Converter
SPI は、このようなフォーマット要件に直接対応していません。それらに直接対処するために、Spring は便利な Formatter
SPI を提供します。これは、クライアント環境の PropertyEditor
実装に代わるシンプルで堅牢な代替手段を提供します。
一般に、java.util.Date
と Long
の間の変換など、汎用の型変換ロジックを実装する必要がある場合は、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
は、Printer
および Parser
ビルドブロックインターフェースから拡張されています。次のリストは、これら 2 つのインターフェースの定義を示しています。
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
パッケージは、java.text.NumberFormat
を使用する Number
オブジェクトをフォーマットするための NumberStyleFormatter
、CurrencyStyleFormatter
、PercentStyleFormatter
を提供します。datetime
パッケージは、java.util.Date
オブジェクトを java.text.DateFormat
でフォーマットするための DateFormatter
を提供します。
次の 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 の課題 (英語) を参照してください。
アノテーション駆動の書式設定
フィールドのフォーマットは、フィールド型またはアノテーションによって構成できます。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
)
Format Annotation API
ポータブルフォーマットアノテーション API は org.springframework.format.annotation
パッケージに含まれています。@NumberFormat
を使用して Double
や Long
などの Number
フィールドをフォーマットし、@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 Expression Language(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 の章の変換とフォーマットを参照してください。