Spring Validation Error Generation
I'm using JSR-303 for validation and the Spring MVC 3. Validation is done manually through validator.validate(bean, errors)
in a controller.
I annotated the name
property of the Foo
class with @NotNull
. When the validation fails, I noticed that the Spring MVC generated the following error codes in the Errors
object.
NotNull.form.foo.name
NotNull.name
NotNull.java.lang.String
NotNull
The way the <form:errors>
tag works is that it will go through all the error codes and look up the default resource bundle until it returns a non-null message. Is there any way to customize these error co开发者_Go百科des or at least the order they are listed?
The reason is that I have a custom ResourceBundle
object that returns a default message derived from a given message code if none is found in a resource bundle text file. Because the tag works its way down on the error code list, it will look up NotNull.form.foo.name
first in this example. The text file of course does not have this entry, so the custom ResourceBundle object will return a default message. The problem is, I already have a message defined in the text file for NotNull
, but the tag will see this as it stands.
If I could somehow generate only one error code, or reverse the order of the error codes, it would work.
Any idea? Thanks.
Could not find simpler solution than this one...
my-servlet.xml
<bean id="handlerAdapter" class="org.opensource.web.StandardAnnotationMethodHandlerAdapter">
</bean>
StandardAnnotationMethodHandlerAdapter.java
public class StandardAnnotationMethodHandlerAdapter extends AnnotationMethodHandlerAdapter {
@Override
protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName) throws Exception {
MyServletRequestDataBinder dataBinder = new MyServletRequestDataBinder(target, objectName);
return dataBinder;
}
}
MyServletRequestDataBinder .java
public class MyServletRequestDataBinder extends ServletRequestDataBinder {
private MessageCodesResolver messageCodesResolver = new MyMessageCodesResolver();
@Override
public void initBeanPropertyAccess() {
super.initBeanPropertyAccess();
BindingResult bindingResult = super.getBindingResult();
if(bindingResult instanceof AbstractBindingResult) {
((AbstractBindingResult)bindingResult).setMessageCodesResolver(messageCodesResolver);
}
}
@Override
public void initDirectFieldAccess() {
super.initDirectFieldAccess();
BindingResult bindingResult = super.getBindingResult();
if(bindingResult instanceof AbstractBindingResult) {
((AbstractBindingResult)bindingResult).setMessageCodesResolver(messageCodesResolver);
}
}
}
MyMessageCodesResolver .java
public class MyMessageCodesResolver extends DefaultMessageCodesResolver {
public static final String NOT_NULL_ERROR_CODE = "NotNull";
@Override
public String[] resolveMessageCodes(String errorCode, String objectName, String field, Class fieldType) {
if(NOT_NULL_ERROR_CODE.equalsIgnoreCase(errorCode)) {
String notNullErrorCode = errorCode + CODE_SEPARATOR + objectName + CODE_SEPARATOR + field;
//notNullErrorCode = postProcessMessageCode(notNullErrorCode);
return new String[] {notNullErrorCode};
}
return super.resolveMessageCodes(errorCode, objectName, field, fieldType);
}
}
I use the following class to do JSR-303 and HibernateValidator bean validation manually with a Spring validator. Perhaps it can be useful.
BeanValidator.java
import java.util.Locale;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
public class BeanValidator implements org.springframework.validation.Validator, InitializingBean {
private static final Logger log = LoggerFactory.getLogger(BeanValidator.class.getName());
private Validator validator;
@Autowired
MessageSource messageSource;
@Override
public void afterPropertiesSet() throws Exception {
ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
validator = validatorFactory.usingContext().getValidator();
}
@Override
public boolean supports(Class clazz) {
return true;
}
@Override
public void validate(Object target, Errors errors) {
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(target);
for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
String propertyPath = constraintViolation.getPropertyPath().toString();
String message;
try {
message = messageSource.getMessage(constraintViolation.getMessage(), new Object[]{}, Locale.getDefault());
} catch (NoSuchMessageException e) {
log.error(String.format("Could not interpolate message \"%s\" for validator. "
+ e.getMessage(), constraintViolation.getMessage()), e);
message = constraintViolation.getMessage();
}
errors.rejectValue(propertyPath, "", message);
}
}
}
SpringMessageSourceMessageInterpolator.java
import javax.validation.MessageInterpolator;
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.NoSuchMessageException;
public class SpringMessageSourceMessageInterpolator extends ResourceBundleMessageInterpolator implements MessageInterpolator, MessageSourceAware, InitializingBean {
@Autowired
private MessageSource messageSource;
@Override
public String interpolate(String messageTemplate, Context context) {
try {
return messageSource.getMessage(messageTemplate, new Object[]{}, Locale.getDefault());
} catch (NoSuchMessageException e) {
return super.interpolate(messageTemplate, context);
}
}
@Override
public String interpolate(String messageTemplate, Context context, Locale locale) {
try {
return messageSource.getMessage(messageTemplate, new Object[]{}, locale);
} catch (NoSuchMessageException e) {
return super.interpolate(messageTemplate, context, locale);
}
}
@Override
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
@Override
public void afterPropertiesSet() throws Exception {
if (messageSource == null) {
throw new IllegalStateException("MessageSource was not injected, could not initialize "
+ this.getClass().getSimpleName());
}
}
}
applicationContext.xml
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource" p:basename="messages"/>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="messageInterpolator">
<bean class="com.company.utils.spring.SpringMessageSourceMessageInterpolator" />
</property>
</bean>
Example bean property
@NotNull(message = "validation.mandatoryField")
private ClientGroup clientGroup;
Example validation
@Controller
public class MyController {
@Autowired
private BeanValidator validator;
@RequestMapping("/foo", method=RequestMethod.POST)
public void processFoo(ModelAttribute("foo") Foo foo, BindingResult result, Model model) {
//...
validator.validate(foo, result);
}
}
精彩评论