Spring Framework provides a ready-to-use, simple validation framework out of the box. In this article, I'll explore how to use Java annotations to make Spring's validation framework even more convenient to use, while providing a basic introduction to Spring validation and Java annotations in general.
Introduction
Spring's data binding and validation framework is simple but functional - all you need to do is implement the Validator
interface. In particular, if you use Spring's MVC framework (and its command/form controllers) then you're probably using Spring validation as well. If not, you should be! Moving your validation outside the controller into a Validator
implementation allows you to declaratively assign and reuse your validation logic.
But while you can declaratively assign and reuse Validator
s with your form or command controllers, doing so only allows you to declare validation on a 'per-class' level, with no field-level granularity.
A typical scenario
For example, consider the typical scenario where you have a LoginForm
class to represent your typical login form with two fields username
and password
. It's trivial to write a LoginFormValidator
that checks that both fields are present and assign that to the controller that handles login.
What if you now need a RegistrationForm
controller, with additional fields such as verifyPassword
and emailAddress
? Well, you could extend LoginFormValidator
or simply add an new Validator
to the registration controller for the additional required fields. All is still good and dandy.
But what about other form or command objects? What if, a PasswordChangeForm
needs only the password
and verifyPassword
fields? How about a UserSearchForm
with only the username
?
You could of course write unique Validator
s for individual fields, but that's a terrible CodeSmell. At this point you know you need to write a more generic validation implementation with field-level granularity.
But, depending upon your approach, you might end up not being able to use Spring's declarative approach to assigning validators to controllers. Or, you might have to implement your own mechanism to allow declarative validation rules using fancy XML. Or use YetAnotherThirdPartyLibrary, which, for a small application or during its initial stages may be overkill and cause unnecessary code bloat.
Annotations to the rescue
Fortunately, Java 5.0 annotations give us a convenient way to mark object elements or fields in a way that lets us roll out an equally simple and extensible, yet more granular validation implementation based on Spring.
The rest of this article details a simplistic approach to this, using the common, if rather trivial example above. At the end of the article I have some thoughts on how one can take this approach and make it more functional and useful.
Our typical LoginForm
package foo.bar; public class LoginForm { private String username; private String password; public String getUsername() { return this.username; }; // snip... }
Nothing exciting happening here, I'll just put it here for reference.
Annotating classes
First we'll want to 'mark' our class so that our annotation-aware validators can easily recognize them later (without having to inspect down to the class members). Let's create our first annotation:
package foo.bar; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Validatable { }
As you can see, there's really nothing there. It's a plain marker annotation. A few things about the annotations that annotate our annotation are in order, though.
The "@Target(ElementType.TYPE)
" annotation tells the compiler that Validatable
should only be applied to Types or classes.
"@Retention(RetentionPolicy.RUNTIME)
" indicates that this annotation should be recorded into the class file and retained by the VM at run-time, enabling it to be introspected. This is probably the most important part of this whole annotation business for our purposes, as it's crucial to how our validator will work later.
We can now use @Validatable
to annotate our classes so they can be identified and validated by our validation framework later. Next, we'll create the @Required
annotation to indicate required fields.
Annotating fields
package foo.bar; import java.lang.annotation.Target; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Required { }
Note that while I said @Required
will be used to annotate required fields, I indicate that it's target should be a METHOD
. In particular, we'll be using @Required
to annotate field or property 'getters'.
There are two reasons for this. One is I'm a lazy bum who likes to get things done the simplest way possible. Next is that if we use @Required
on a class' private fields then we'd have to do double work to introspect the class just to find our annotation(s) (as opposed to being able to use Spring's BeanUtils
to help us out). So instead, I opt to use @Required
on the getField() methods.
Annotations in action
Armed with these two annotations, let's go ahead and apply them to our LoginForm
.
@Validatable public class LoginForm { // ...snip @Required public String getUsername() { return this.username; }; // ...snip... @Required public String getUsername() { return this.username; }; // snip... }
As you can see, writing annotations is quite painless, especially simple marker annotations with no parameters (or elements) like ours.
Also, using annotations in one's code doesn't require much work, either (just remember to import
them if they're in a different package). In fact, annotations can lend to better code clarity - after all, that's what they're there for in the first place.
A Spring Validator in 3 easy steps
Now let's make this magic happen. We'll write a RequiredAnnotationValidator
that uses introspection to find our annotations, and perform validation on the required fields.
Step 1: Implement Validator
public class RequiredAnnotationValidator implements Validator { // snip...
Because we're implementing Spring's Validator
interface, we need to implement its two primary methods supports()
and validate()
.
Step 2: Implement supports()
What classes should our RequiredAnnotationValidator
support? Why, classes annotated as @Validatable
, of course. How do we find out if a class is annotated as @Validatable
using introspection? Why, it's almost trivial:
@SuppressWarnings("unchecked") public final boolean supports(final Class clazz) { return clazz.isAnnotationPresent(Validatable.class); }
As you can see if we had only used our @Required
annotation, our validator would have to do a lot more work - go through the methods of the given Class clazz
sequentially, looking for any methods annotated as @Required
and returning true
once it finds one. Annotating the class itself is simpler and faster.
A little note about the @SuppressWarnings
above. If you're using Eclipse IDE, the line "clazz.isAnnotationPresent(...)
" will generate a warning because we're calling a method on the raw type Class
(and not a parameterized version of the generic type Class < T >
). It's kind of a nuisance - though we can get rid of the warning with a fugly cast: "Class<?> c = clazz
" - so we tell Eclipse to just shut up.
Step 3: Implement validate()
Now to the meat of the matter, implementing validate()
. Now that you've seen how supports()
works, you should have a general idea of how validate()
is supposed to work.
First we'll want a list of getter methods. We could use standard Java Reflection, or, since we're already using Spring:
public final void validate(final Object obj, final Errors errors) { // list the object's properties final PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(obj.getClass()); ...
We can rely on BeanUtils.getPropertyDescriptors()
for convenience since, by our own convention, we're looking for annotations in property getter methods. As I mentioned earlier, had we annotated private fields, we'd have to use Class.getDeclaredFields()
, iterate through the fields looking for corresponding accessor methods, etc. Our approach is a lot less painful.
What do we do with each property descriptor? First check if the property has a read method, and if that method is annotated as @Required
(since we're using Java 5.0, we'll go ahead and use an enhanced for
loop):
for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) { // see if the property (getter) is annotated as required final Method method = propertyDescriptor.getReadMethod(); if (method != null && method.isAnnotationPresent(Required.class)) {
At this point we know that the field foo
is required because the method getFoo()
was annotated with @Required
. Now all we have to do is check that its value is not null
, or, since we want to be a little smart, check that it's not the empty string either (""
). Fortunately, once again Spring provides utility code in ValidationUtils
to do just the job:
final String field = propertyDescriptor.getName(); ValidationUtils.rejectIfEmpty(errors, field, field + ".required");
Here we just told Spring to check if the given field is empty, and if it is, reject it with the error code "fieldname.required" and place that error into the passed in errors
object.
The complete validator
Here's a more-or-less complete look at our RequiredAnnotationValidator
(minus any import
statements):
package foo.bar; public class RequiredAnnotationValidator implements Validator { @SuppressWarnings("unchecked") public final boolean supports(final Class clazz) { return clazz.isAnnotationPresent(Validatable.class); } public final void validate(final Object obj, final Errors errors) { // list the object's properties final PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(obj.getClass()); for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) { // see if the property (getter) is annotated as required final Method method = propertyDescriptor.getReadMethod(); if (method != null && method.isAnnotationPresent(Required.class)) { // reject with "field.required" if null/empty final String field = propertyDescriptor.getName(); ValidationUtils.rejectIfEmpty(errors, field, field + ".required"); } } } }
Voila!
Annotation-based validation based on Spring's validation framework in a couple of dozen lines of code, how's that? Here's how we'd use it in an sample Spring application context XML descriptor file:
<bean name="requiredAnnotationValidator" class="foo.bar.RequiredAnnotationValidator"/> <!-- e.g., foo.bar.LoginController extends SimpleFormController --> <bean name="loginController" class="foo.bar.LoginController"> <property name="commandClass"><value>foo.bar.LoginForm</value></property> <property name="validator"><ref bean="requiredAnnotationValidator"/></property> </bean>
If we need to use more than one validator for LoginController, then we simply use the plural validators
property of BaseCommandController
.
Conclusion
Simple, extensible and granular declarative validation is possible using the Spring framework, aided by Java annotations.
There are a lot of more things we can do from here. We could use an annotation element as a parameter to declaratively specify the error code for our required field (as opposed to a hard-coded "field.required"). We can go ahead and create other annotations, for example, @MaxLength
with a length parameter.
One drawback to our approach is the fact that the validation has to perform introspection every time it's asked to validate an object. For form command object binding and validation, this isn't such a problem. However, this can lead to performance issues especially if the validation is called repeatedly on a large collection of objects.
One enhancement I could think of is to have the validators cache the introspection results so they don't have to do it every time (and do so in a thread-safe manner).
This article has outlined a gentle introduction Spring validation as well as Java annotations. I hope now you know how easy they both can be, and that you're now as excited as I am on their added posssiblities.
Alistair Israel started learning programming with BASIC on a TRS-80. That was a long time ago. He's still learning.