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.text.DateFormat
を使用して java.util.Date
オブジェクトをフォーマットするための DateFormatter
と、@DurationFormat.Style
列挙で定義されたさまざまなスタイル (Format Annotation API を参照) で Duration
オブジェクトをフォーマットするための DurationFormatter
を提供します。
次の 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
org.springframework.format.annotation
パッケージには、ポータブルフォーマットアノテーション API が存在します。@NumberFormat
を使用して Double
や Long
などの Number
フィールドをフォーマットしたり、@DurationFormat
を使用して Duration
フィールドを ISO-8601 および簡略化されたスタイルでフォーマットしたり、@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
)
詳細については、@DateTimeFormat
(Javadoc) 、@DurationFormat
(Javadoc) 、@NumberFormat
(Javadoc) の javadoc を参照してください。
スタイルベースの書式設定と解析は、Java ランタイムに応じて変更される可能性のあるロケール依存のパターンに依存します。具体的には、日付、時刻、数値の解析と書式設定に依存するアプリケーションは、JDK 20 以降で実行すると、互換性のない動作の変更が発生する可能性があります。 ISO 標準形式またはユーザーが制御する具体的なパターンを使用すると、システムやロケールに依存しない信頼性の高い日付、時刻、数値の解析と書式設定が可能になります。
詳細については、Spring Framework wiki の JDK 20 以降での日付と時刻のフォーマット [GitHub] (英語) ページを参照してください。 |
FormatterRegistry
SPI
FormatterRegistry
は、フォーマッタとコンバーターを登録するための SPI です。FormattingConversionService
は、ほとんどの環境に適した FormatterRegistry
の実装です。たとえば、FormattingConversionServiceFactoryBean
を使用して、このバリアントを Spring Bean としてプログラムまたは宣言的に構成できます。この実装は 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
は、FormatterRegistry を介してフォーマッターとコンバーターを登録するための SPI です。次のリストは、そのインターフェース定義を示しています。
package org.springframework.format;
public interface FormatterRegistrar {
void registerFormatters(FormatterRegistry registry);
}
FormatterRegistrar
は、日付の書式設定など、特定の書式設定カテゴリに関連する複数のコンバーターとフォーマッターを登録する場合に役立ちます。また、宣言型の登録が不十分な場合にも役立ちます。たとえば、フォーマッタを、それ自体の <T>
とは異なる特定のフィールド型でインデックス付けする必要がある場合や、Printer
/Parser
ペアを登録する場合などです。次のセクションでは、コンバーターとフォーマッターの登録について詳しく説明します。
Spring MVC でのフォーマットの構成
Spring MVC の章の変換とフォーマットを参照してください。