Notice
I wrote this article and was originally published on Qiita on 9 July 2021.
To perform model binding validation of controller, one of the solution is register validator on controller.
1. Write your own validator
You validator should extend org.springframework.validation.Validator interface. Below is a example.
public class MessageDtoValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return MessageDto.class.isAssignableFrom(clazz);
}
@Override
public void validate(Object target, Errors errors) {
// target is model being checked and errors register into errors instance
ValidationUtils.rejectIfEmpty(errors, "publishDate", "publishDate.empty");
ValidationUtils.rejectIfEmpty(errors, "description", "description.empty");
}
}
2. Register your validator
You should register your validator on function annotated with org.springframework.web.bind.annotation.InitBinder annotation with org.springframework.validation.DataBinder.setValidator(Validator) function.
// Call everytime when recevies request
@InitBinder
public void initBinder(WebDataBinder binder) {
...
binder.setValidator(new MessageDtoValidator())
...
}
org.springframework.validation.Validator.supports(Class<?>) of your validator will be called when calling setValidator() to verify is your validator supports model binded.
3. Specify model argument you want to verify and define corresponding action
You should add javax.validation.Valid (or org.springframework.validation.annotation.Validated) annotation in addition with org.springframework.web.bind.annotation.ModelAttribute annotation on model argument you want to verify. And specify actions when binding error is found.
@PostMapping("/{messageId}/save")
public String saveEditMessage(@PathVariable("messageId") long id, @Valid @ModelAttribute MessageDto message, BindingResult errors) {
if (errors.hasErrors()) {
// your action
...
}
}
After binding model, framework will check if validation annotation exists on model argument by org.springframework.web.method.annotation.ModelAttributeMethodProcessor.determineValidationHints(Annotation) function.
/**
* Determine any validation triggered by the given annotation.
* @param ann the annotation (potentially a validation annotation)
* @return the validation hints to apply (possibly an empty array),
* or {@code null} if this annotation does not trigger any validation
* @since 5.1
*/
@Nullable
private Object[] determineValidationHints(Annotation ann) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
if (hints == null) {
return new Object[0];
}
return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
}
return null;
}
And then actual validation logic org.springframework.validation.Validator.validate(Object, Errors) will be called by org.springframework.web.method.annotation.ModelAttributeMethodProcessor.validateIfApplicable(WebDataBinder, MethodParameter) function.
Example
You can find actual implementation from my notice board example application.