Springでは、変換、表示用フォーマット、オブジェクトバインディング、バリデーションを別々の操作として扱います。そのため、リクエストパラメーターを型付きプロパティに変換しながら、ドメインオブジェクトをWebコントローラーに結合せずに、構造化されたエラーを生成できます。
変換とフォーマット #
ConversionServiceは、型変換のファサードです。変換が利用可能かどうかを最初に確認し、その後、rawクラスまたはTypeDescriptorによって変換できます。TypeDescriptorはアノテーションとネストされたジェネリック情報を保持します。
拡張インターフェースは、それぞれ異なる要件に対応します。
Converter<S, T>は、1つの強く型付けされた変換を実装します。ConverterFactory<S, R>は、ターゲットのサブタイプ群に対応するコンバーターを生成します。たとえば、Stringから任意のenum型への変換です。GenericConverterは、変換可能なソース/ターゲットの組み合わせの集合を宣言し、コンテキスト依存の変換のためにTypeDescriptor値を受け取ります。ConditionalGenericConverterは、特定のソースとターゲットに適合するかどうかも判断できます。
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に記録し、バリデーターを呼び出すことができます。
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を通じて失敗を報告します。
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 で閲覧・コメントする(投稿にはログインが必要です)