Spring の検証インターフェースを使用した検証

Spring は、オブジェクトの検証に使用できる Validator インターフェースを備えています。Validator インターフェースは Errors オブジェクトを使用して機能するため、検証中にバリデーターは Errors オブジェクトに検証エラーを報告できます。

次の小さなデータオブジェクトの例を考えてみましょう。

  • Java

  • Kotlin

public class Person {

	private String name;
	private int age;

	// the usual getters and setters...
}
class Person(val name: String, val age: Int)

次の例では、org.springframework.validation.Validator インターフェースの以下の 2 つのメソッドを実装することにより、Person クラスの検証動作を提供します。

  • supports(Class): この Validator は、提供された Class のインスタンスを検証できますか?

  • validate(Object, org.springframework.validation.Errors): 指定されたオブジェクトを検証し、検証エラーの場合、指定された Errors オブジェクトに登録します。

Spring Framework が提供する ValidationUtils ヘルパークラスを知っている場合は特に、Validator の実装は非常に簡単です。次の例では、Person インスタンスに Validator を実装しています。

  • Java

  • Kotlin

public class PersonValidator implements Validator {

	/**
	 * This Validator validates only Person instances
	 */
	public boolean supports(Class clazz) {
		return Person.class.equals(clazz);
	}

	public void validate(Object obj, Errors e) {
		ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
		Person p = (Person) obj;
		if (p.getAge() < 0) {
			e.rejectValue("age", "negativevalue");
		} else if (p.getAge() > 110) {
			e.rejectValue("age", "too.darn.old");
		}
	}
}
class PersonValidator : Validator {

	/**
	 * This Validator validates only Person instances
	 */
	override fun supports(clazz: Class<*>): Boolean {
		return Person::class.java == clazz
	}

	override fun validate(obj: Any, e: Errors) {
		ValidationUtils.rejectIfEmpty(e, "name", "name.empty")
		val p = obj as Person
		if (p.age < 0) {
			e.rejectValue("age", "negativevalue")
		} else if (p.age > 110) {
			e.rejectValue("age", "too.darn.old")
		}
	}
}

ValidationUtils クラスの staticrejectIfEmpty(..) メソッドは、null または空の文字列である場合、name プロパティを拒否するために使用されます。ValidationUtils javadoc を見て、前に示した例以外にどのような機能が提供されているかを確認してください。

リッチオブジェクトの各ネストされたオブジェクトを検証するために単一の Validator クラスを実装することは確かに可能ですが、独自の Validator 実装でオブジェクトの各ネストされたクラスの検証ロジックをカプセル化する方が良い場合があります。「リッチ」オブジェクトの簡単な例は、2 つの String プロパティ(1 番目と 2 番目の名前)と複雑な Address オブジェクトで構成される Customer です。Address オブジェクトは Customer オブジェクトとは独立して使用できるため、別個の AddressValidator が実装されています。CustomerValidator で AddressValidator クラスに含まれるロジックをコピーアンドペーストに頼らずに再利用したい場合、次の例に示すように、CustomerValidator 内で AddressValidator を依存性注入またはインスタンス化できます。

  • Java

  • Kotlin

public class CustomerValidator implements Validator {

	private final Validator addressValidator;

	public CustomerValidator(Validator addressValidator) {
		if (addressValidator == null) {
			throw new IllegalArgumentException("The supplied [Validator] is " +
				"required and must not be null.");
		}
		if (!addressValidator.supports(Address.class)) {
			throw new IllegalArgumentException("The supplied [Validator] must " +
				"support the validation of [Address] instances.");
		}
		this.addressValidator = addressValidator;
	}

	/**
	 * This Validator validates Customer instances, and any subclasses of Customer too
	 */
	public boolean supports(Class clazz) {
		return Customer.class.isAssignableFrom(clazz);
	}

	public void validate(Object target, Errors errors) {
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
		Customer customer = (Customer) target;
		try {
			errors.pushNestedPath("address");
			ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
		} finally {
			errors.popNestedPath();
		}
	}
}
class CustomerValidator(private val addressValidator: Validator) : Validator {

	init {
		if (addressValidator == null) {
			throw IllegalArgumentException("The supplied [Validator] is required and must not be null.")
		}
		if (!addressValidator.supports(Address::class.java)) {
			throw IllegalArgumentException("The supplied [Validator] must support the validation of [Address] instances.")
		}
	}

	/*
	* This Validator validates Customer instances, and any subclasses of Customer too
	*/
	override fun supports(clazz: Class<>): Boolean {
		return Customer::class.java.isAssignableFrom(clazz)
	}

	override fun validate(target: Any, errors: Errors) {
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required")
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required")
		val customer = target as Customer
		try {
			errors.pushNestedPath("address")
			ValidationUtils.invokeValidator(this.addressValidator, customer.address, errors)
		} finally {
			errors.popNestedPath()
		}
	}
}

検証エラーは、バリデーターに渡される Errors オブジェクトに報告されます。Spring Web MVC の場合、<spring:bind/> タグを使用してエラーメッセージをインスペクションできますが、Errors オブジェクトを自分でインスペクションすることもできます。提供するメソッドの詳細については、javadoc を参照してください

バリデータは、バインディングプロセスを伴わずに、特定のオブジェクトの即時検証のためにローカルに呼び出されることもあります。6.1 では、これが新しい Validator.validateObject(Object) メソッドによって簡略化され、デフォルトで使用できるようになりました。このメソッドは、インスペクション可能な単純な Errors 表現を返します。通常は、エラーサマリーメッセージを例外 (たとえば、validator.validateObject(myObject).failOnError(IllegalArgumentException::new)) に変換するには、hasErrors() または新しい failOnError メソッドを呼び出します。