Spring MVC and JSR 303 - Manual Validation
I'm using Spring MVC 3 开发者_StackOverflow中文版and JSR 303. I have a form backing object that has beans of different types in it. Depending on a request parameter value, I'll have to choose a bean to validate and save. I can't use @Valid
for validation since the bean to validate is not known until the runtime.
I was able to inject a javax.validation.Validator
to the controller, but I'm not sure how to validate a bean with it and store any errors in a BindingResult/Error
in a "Spring way".
I need to do it in the handler method, rather than an initBinder
method, because of request mapping.
[edit]
The problem I'm having with validate(Object, Errors)
is that it doesn't recognize nested beans. The bean to validate is accessed through foo.getBar().getBean(), where the foo is the form backing object. When I do validate(foo.getBar().getBean(), errors)
, I get the following error message.
JSR-303 validated property 'property-name' does not have a corresponding accessor for Spring data binding
Has anyone done something like this before? Thanks.
Yup, the magic class you're looking for is org.springframework.validation.beanvalidation.SpringValidatorAdapter
This class gets a javax.validation.Validator injected into it and contains the code for, as its name implies, 'adapting' the output back into the familiar Errors
object. It's what's being used internally to do the processing when you put @Valid
on a method parameter.
You can get them directly by adding an explicit LocalValidatorFactoryBean
in your dispatcher servlet. Just inject an instance of that as an instance of the standard Spring Validator interface and use it like you would any 'pre jsr-303' spring validation provider.
Just a guess, but have you tried
errors.pushNestedPath("bar.bean"); // Path to the nested bean
validate(foo.getBar().getBean(), errors)
errors.popNestedPath();
That's how BindingResult
is usually used for validation of the nested beans.
The way the I've seen this done is to use a standard JSR-303 validator (whatever you're already injecting in) to get the violations (i.e. Set<ConstraintViolaion<T>>
)
Then use code similar to that inside LocalValidatorFactoryBean to convert between those violations and Spring Errors:
public static <T> void convert(Errors errors, Collection<ConstraintViolation<T>> violations) {
for (ConstraintViolation<?> violation : violations) {
String field = violation.getPropertyPath().toString();
FieldError fieldError = errors.getFieldError(field);
if (fieldError == null || !fieldError.isBindingFailure()) {
errors.rejectValue(field, violation.getConstraintDescriptor().getAnnotation().annotationType()
.getSimpleName(), getArgumentsForConstraint(errors.getObjectName(), field, violation
.getConstraintDescriptor()), violation.getMessage());
}
}
}
private static Object[] getArgumentsForConstraint(String objectName, String field,
ConstraintDescriptor<?> descriptor) {
List<Object> arguments = new LinkedList<Object>();
String[] codes = new String[] { objectName + Errors.NESTED_PATH_SEPARATOR + field, field };
arguments.add(new DefaultMessageSourceResolvable(codes, field));
arguments.addAll(descriptor.getAttributes().values());
return arguments.toArray(new Object[arguments.size()]);
}
精彩评论