Validation by Using Spring’s Validator Interface

Spring 提供了一个 Validator 接口,您可以使用它来验证对象。Validator 接口通过使用 Errors 对象工作,因此在验证过程中,验证器可以将验证失败报告给 Errors 对象。

Spring features a Validator interface that you can use to validate objects. The Validator interface works by using an Errors object so that, while validating, validators can report validation failures to the Errors object.

请考虑以下小型数据对象的示例:

Consider the following example of a small data object:

  • 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 类提供验证行为:

The next example provides validation behavior for the Person class by implementing the following two methods of the org.springframework.validation.Validator interface:

  • supports(Class): Can this Validator validate instances of the supplied Class?

  • validate(Object, org.springframework.validation.Errors): Validates the given object and, in case of validation errors, registers those with the given Errors object.

实现 Validator 相当简单,特别是如果你知道 Spring Framework 也提供的 ValidationUtils 帮助类。以下示例为 Person 实例实现 Validator

Implementing a Validator is fairly straightforward, especially when you know of the ValidationUtils helper class that the Spring Framework also provides. The following example implements Validator for Person instances:

  • 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,看看除了之前显示的示例之外它还提供了哪些功能。

The static rejectIfEmpty(..) method on the ValidationUtils class is used to reject the name property if it is null or the empty string. Have a look at the ValidationUtils javadoc to see what functionality it provides besides the example shown previously.

尽管肯定可以实现一个 Validator 类来验证复合对象中的每个嵌套对象,但最好将每个嵌套对象的验证逻辑封装在它自己的 Validator 实现中。“复合” 对象的简单示例将是一个 Customer,它由两个 String 属性(名和姓)和一个复杂的 Address 对象组成。Address 对象可以独立于 Customer 对象使用,因此已经实现了不同的 AddressValidator。如果希望 CustomerValidator 重用 AddressValidator 类中包含的逻辑而不使用复制粘贴,则你可以在 CustomerValidator 中依赖注入或实例化 AddressValidator,如下例所示:

While it is certainly possible to implement a single Validator class to validate each of the nested objects in a rich object, it may be better to encapsulate the validation logic for each nested class of object in its own Validator implementation. A simple example of a “rich” object would be a Customer that is composed of two String properties (a first and a second name) and a complex Address object. Address objects may be used independently of Customer objects, so a distinct AddressValidator has been implemented. If you want your CustomerValidator to reuse the logic contained within the AddressValidator class without resorting to copy-and-paste, you can dependency-inject or instantiate an AddressValidator within your CustomerValidator, as the following example shows:

  • 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 中找到。

Validation errors are reported to the Errors object passed to the validator. In the case of Spring Web MVC, you can use the <spring:bind/> tag to inspect the error messages, but you can also inspect the Errors object yourself. More information about the methods it offers can be found in the javadoc.

还可以本地调用验证器来立即验证给定对象,而不需要绑定过程。从 6.1 开始,这已通过新的 Validator.validateObject(Object) 方法简化,该方法现在默认可用,返回一个简单的 Errors 表示,可以对其进行检查:通常调用 hasErrors() 或新方法 failOnError 将错误摘要消息转换为异常(例如 validator.validateObject(myObject).failOnError(IllegalArgumentException::new))。

Validators may also get locally invoked for the immediate validation of a given object, not involving a binding process. As of 6.1, this has been simplified through a new Validator.validateObject(Object) method which is available by default now, returning a simple ´Errors` representation which can be inspected: typically calling hasErrors() or the new failOnError method for turning the error summary message into an exception (e.g. validator.validateObject(myObject).failOnError(IllegalArgumentException::new)).