Springの変換とバリデーション

最終更新: 2023-11-28 10:33:04 +0800 +0800
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

Springでは、変換、表示用フォーマット、オブジェクトバインディング、バリデーションを別々の操作として扱います。そのため、リクエストパラメーターを型付きプロパティに変換しながら、ドメインオブジェクトをWebコントローラーに結合せずに、構造化されたエラーを生成できます。

変換とフォーマット #

ConversionServiceは、型変換のファサードです。変換が利用可能かどうかを最初に確認し、その後、rawクラスまたはTypeDescriptorによって変換できます。TypeDescriptorはアノテーションとネストされたジェネリック情報を保持します。

拡張インターフェースは、それぞれ異なる要件に対応します。

  • Converter<S, T>は、1つの強く型付けされた変換を実装します。
  • ConverterFactory<S, R>は、ターゲットのサブタイプ群に対応するコンバーターを生成します。たとえば、Stringから任意のenum型への変換です。
  • GenericConverterは、変換可能なソース/ターゲットの組み合わせの集合を宣言し、コンテキスト依存の変換のためにTypeDescriptor値を受け取ります。
  • ConditionalGenericConverterは、特定のソースとターゲットに適合するかどうかも判断できます。
java
final class StringToOrderId implements Converter<String, OrderId> {
    @Override
    public OrderId convert(String source) {
        return new OrderId(UUID.fromString(source.strip()));
    }
}

Formatter<T>は表示層に属します。そのPrinterおよびParser操作はLocaleを使用するため、数値、日付、人間向けテキストに適しています。コンテキストに依存しない型変更にはコンバーターを使用し、テキスト表現がロケールまたはUIポリシーに依存する場合にはフォーマッターを使用します。両方ともFormatterRegistryを通じて登録します。DefaultFormattingConversionServiceは、レジストリとコンバージョンサービスの両方の契約を実装します。

レガシーJavaBeansプロパティエディター #

Springは互換性のために、現在もjava.beans.PropertyEditorをサポートしています。PropertyEditorは状態を持ち、主にテキストと内部値を介して動作し、スレッドセーフではありません。そのため、コンテナはインスタンスを共有せず、バインディング操作ごとにエディターを生成します。

JavaBeansの規約では、Typeの隣にあるTypeEditorという名前のエディターを検索します。また、PropertyEditorManagerは明示的な登録と検索パスもサポートします。Springは、型全体またはプロパティパス単位で登録するためのPropertyEditorRegistryと、再利用可能な登録ロジックのためのPropertyEditorRegistrarを追加しています。BeanWrapperとDataBinderは、これらのレジストリを公開します。

新しいアプリケーションの変換にはConversionServiceを使用してください。1つを必要とするJavaBeans APIと統合する場合は、エディターが適しています。HTTPメッセージボディは通常、プロパティエディターによってデコードされません。Spring MVCは、JSONなどのボディ形式に対してHttpMessageConverterを使用します。

DataBinderによるバインディング #

DataBinderは、入力されたプロパティ値をターゲットオブジェクトに適用します。ネストされたプロパティパスを解決し、変換またはプロパティエディターを呼び出し、失敗をBindingResultに記録し、バリデーターを呼び出すことができます。

java
var binder = new DataBinder(command, "order");
binder.setAllowedFields("customerId", "quantity");
binder.setConversionService(conversionService);
binder.addValidators(new OrderValidator());
binder.bind(propertyValues);
binder.validate();

BindingResult result = binder.getBindingResult();

信頼できない入力のバインディングは、セキュリティ境界です。広範なドメインオブジェクトを公開するのではなく、許可するフィールドを定義するか、専用のコマンドオブジェクトを使用してください。変換に失敗すると、バインディング操作の外部に例外を送出する代わりに、フィールドエラーになります。ネストされたパスとコレクションの自動拡張は設定可能であり、攻撃者が制御する入力に対しては上限を設ける必要があります。

SpringのValidator契約 #

SpringのValidatorは、クラスをサポートするかどうかを宣言し、Errorsを通じて失敗を報告します。

java
final class OrderValidator implements Validator {
    public boolean supports(Class<?> type) {
        return OrderCommand.class.isAssignableFrom(type);
    }

    public void validate(Object target, Errors errors) {
        var order = (OrderCommand) target;
        ValidationUtils.rejectIfEmpty(errors, "customerId", "required");
        if (order.quantity() <= 0) {
            errors.rejectValue("quantity", "positive");
        }
    }
}

Errors.pushNestedPath()とpopNestedPath()を使用すると、複合バリデーターが正しいパスで子のエラーを報告できます。エラーコードは論理識別子であり、メッセージ解決によって後からローカライズされたテキストを選択できます。

Jakarta Bean Validation #

Spring 6は、jakarta.validation Bean Validation 3.xと統合します。LocalValidatorFactoryBeanはHibernate Validatorなどのプロバイダーをブートストラップし、JakartaのValidatorFactory/ValidatorへのアクセスとSpringのValidatorインターフェースの両方を実装します。Springのライフサイクル管理に参加し、シャットダウン時に基盤となるファクトリを閉じます。

MVCバインディングでは、@Validまたは@Validatedを通じて制約アノテーションを適用できます。メソッドバリデーションでは、Springのメソッドバリデーションインフラストラクチャを通じて、メソッドパラメーターと戻り値に制約を適用します。バリデーションは変換の代替ではありません。値はまず、制約が作用する型で表現されている必要があります。

主なリファレンス #

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)