Wednesday, 21 April 2010

Using JSR 303 with "classic" Spring Validators (enter the SpringValidatorAdapter)

Spring 3.0 has a number of new features, one of which is support for JSR303. JSR303 adds annotation-driven validation for Java Beans which is intended to replace hand-coded validation code. This has obvious ramifications for Spring MVC as a key part of this framework includes support for validation of web forms using hand-coded Validator implementations. I'm gradually upgrading a Spring 2.x MVC application to Spring 3.0 at the moment, with the emphasis on gradual. So while I'd like to use JSR 303, I'm not ready yet to re-code the 50 odd Controller implementations to use the new annotation-driven style (even though I hate seeing warnings in Eclipse). Luckily, Spring makes it really easy to have the best of both worlds using the SpringValidatorAdapter. Put simply, this class wraps the JSR303 adapter and provides convenience methods to enable you to easily support the old style Validator interface methods. You can then use the annotations in your command/form objects and the old-school Validators in your Controller classes. Here is a simple example command object using the JSR 303 annotations. They are pretty self-evident and there are loads of blogs that explain what they do.
public class MyCommand implements Serializable {

private static final long serialVersionUID = 42L;

@NotNull
@Min(value=1L)
private Integer projectId;

@NotNull
@Min(value=1L)
private Integer employeeId;

@NotNull
@Min(value=1L)
@Max(value=999L)
private BigDecimal hoursToComplete;

     // Constructors, accessors, mutators, equals, hashcode omitted :-)
}
The Validator for this class is then very simple. Create a Validator implementation as per usual, and then inject the SpringValidatorAdapter class into it. I've done this through the constructor, as I am trying to appear clever and multi-threaded by marking the SpringValidatorAdapter instance as final. Oh yeah, feel my muscles.
public class MyCommandCommandValidator implements Validator {

private final SpringValidatorAdapter validator;

public MyCommandCommandValidator(SpringValidatorAdapter validator) {
 super();
 this.validator = validator;
}

@SuppressWarnings("unchecked")
@Override
public boolean supports(Class givenClass) {
 return validator.supports(givenClass);
}

@Override
public void validate(Object target, Errors errors) {
 validator.validate(target, errors);
 
 // More custom validation here

}
}
Note that you could just use the SpringValidationAdapter directly (without wrapping it in your own Validator implementation) if you are only doing JSR303 validation. It's also possible to write your own JSR303 custom validation routines, which might be a better long term architecture than the mixed approach above. The Spring LocalValidatorFactoryBean implements the main JSR303 Validator interface as well as SpringValidatorAdapter. So the Spring configuration is simple. You do need to have the Hibernate Validator reference implementation available on the classpath or an alternative.



   

Job done! The only other thing to note is that the message codes are set by the JSR303 Validator, so you may have less control over them. For example:

public void testValidateNotNullFields() throws Exception {

 // Perform validation
 validator.validate(command, errors);

 // Check error count
 assertEquals(Integer.valueOf(3), Integer.valueOf(errors.getErrorCount()));
 
 // Test employeeId field
 FieldError employeeIdError = errors.getFieldError("employeeId");
 assertNotNull(employeeIdError);
 assertEquals("NotNull", employeeIdError.getCode());

}

In this test, the employeeId field was not set in the command. The Validator created a field error under key "employeeId" with the error code "NotNull" as it was the @NotNull constraint that was violated (and not in a good way). In summary - using JSR303 with "classic" validators is really easy thanks to the SpringValidatorAdapter class. Woot.

3 comments:

  1. Thanks Pat, this is a good explanation of how to combine JSR-303 and Spring Validation without using Spring MVC.

    The Spring docs are very vague on this subject!

    ReplyDelete
  2. where does errors in validator.validate(command, errors); comes from ?

    ReplyDelete