使用 Spring 的 Validator 介面進行驗證

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 介面的以下兩個方法,為 Person 類別提供驗證行為

  • supports(Class):此 Validator 可以驗證提供的 Class 的實例嗎?

  • validate(Object, org.springframework.validation.Errors):驗證給定的物件,並且在發生驗證錯誤時,向給定的 Errors 物件註冊這些錯誤。

實作 Validator 非常簡單,尤其是當您知道 Spring Framework 也提供的 ValidationUtils 輔助類別時。以下範例為 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 類別上的 static rejectIfEmpty(..) 方法用於拒絕 name 屬性,如果它是 null 或空字串。查看 ValidationUtils javadoc,以了解它除了先前顯示的範例之外還提供了哪些功能。

雖然確實可以實作單一 Validator 類別來驗證豐富物件中的每個巢狀物件,但最好將每個物件的巢狀類別的驗證邏輯封裝在其自己的 Validator 實作中。「豐富」物件的簡單範例是 Customer,它由兩個 String 屬性(名字和姓氏)和一個複雜的 Address 物件組成。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 表示:通常呼叫 hasErrors() 或新的 failOnError 方法,以將錯誤摘要訊息轉換為例外(例如,validator.validateObject(myObject).failOnError(IllegalArgumentException::new))。