Spring's features a Validator
interface that you can
use to validate objects. The Validator
interface works using
an Errors
object so that while validating, validators can report
validation failures to the Errors
object.
Let's consider a small data object:
public class Person { private String name; private int age; // the usual getters and setters... }
We're going to provide 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
Implementing a Validator
is fairly straightforward,
especially when you know of the ValidationUtils
helper class
that the Spring Framework also provides.
public class PersonValidator implements Validator { /** * This Validator validates just 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"); } } }
As you can see, 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 Javadoc for the ValidationUtils
class to see
what functionality it provides besides the example shown previously.
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 second name) and a complex Address
object.
Address
objects may be used independently of
Customer
objects, and so a distinct
AddressValidator
has been implemented. If you want your
CustomerValidator
to reuse the logic contained within the
AddressValidator
class without recourse to copy-n-paste you can
dependency-inject or instantiate an AddressValidator
within your
CustomerValidator
, and use it like so:
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(); } } }
Validation errors are reported to the Errors
object passed to the validator. In case of Spring Web MVC you can use
<spring:bind/>
tag to inspect the error messages, but
of course you can also inspect the errors object yourself. More information about
the methods it offers can be found from the Javadoc.