I\'m adding a user validator using the initBinder
method:
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.setVa
You need to set the value of the @InitBinder
annotation to the name of the command you want it to validate. This tells Spring what to apply the binder to; without it, Spring will try to apply it to everything. This is why you're seeing that exception: Spring is trying to apply the binder - with your UserValidator
- to a parameter of type CustomerPayment
.
In your specific case, it looks like you need something like:
@InitBinder("user")
protected void initBinder(WebDataBinder binder) {
binder.setValidator(new UserValidator());
}
To your second question, as Rigg802 explained, Spring does not support attaching multiple validators to a single command. You can, however, define multiple @InitBinder
methods for different commands. So, for example, you could put the following in a single controller and validate your user and payment parameters:
@InitBinder("user")
protected void initUserBinder(WebDataBinder binder) {
binder.setValidator(new UserValidator());
}
@InitBinder("payment")
protected void initPaymentBinder(WebDataBinder binder) {
binder.setValidator(new CustomerPaymentValidator());
}
Multiple validator on one command is supported with Spring MVC 4.x now. You could use this snippet code:
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new UserValidator(), new CustomerPaymentValidator());
}
It's a bit tricky to do, 1 controller has only 1 validator on 1 command object. you need to create a "Composite Validator" that will get all the validators and run them seperately.
Here is a tutorial that explains how to do it: using multiple validators
There is a simple hack, always return true
in supports
method, and delegate the class checking to validate
. Then basically you can add multiple validator in the initBinder
without issue.
@Component
public class MerchantRegisterValidator implements Validator {
@Autowired
private MerchantUserService merchantUserService;
@Autowired
private MerchantCompanyService merchantCompanyService;
@Override
public boolean supports(Class<?> clazz) {
return true; // always true
}
@Override
public void validate(Object target, Errors errors) {
if (!XxxForm.getClass().equals(target.getClass()))
return; // do checking here.
RegisterForm registerForm = (RegisterForm) target;
MerchantUser merchantUser = merchantUserService.getUserByEmail(registerForm.getUserEmail());
if (merchantUser != null) {
errors.reject("xxx");
}
MerchantCompany merchantCompany = merchantCompanyService.getByRegno(registerForm.getRegno());
if (merchantCompany != null) {
errors.reject("xxx");
}
}
}
The safest way is to add a generic validator handling that Controller:
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setValidator(new GenericControllerOneValidator());
}
Then, in the generic validator you can support multiple request body models and based of the instance of the object, you can invoke the appropriate validator:
public class GenericValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return ModelRequestOne.class.equals(aClass)
|| ModelRequestTwo.class.equals(aClass);
}
@Override
public void validate(Object body, Errors errors) {
if (body instanceof ModelRequestOne) {
ValidationUtils.invokeValidator(new ModelRequestOneValidator(), body, errors);
}
if (body instanceof ModelRequestTwo) {
ValidationUtils.invokeValidator(new ModelRequestTwoValidator(), body, errors);
}
}
}
Then you add your custom validations inside for each model validator implementatios. ModeRequestOneValidator
and ModeRequestTwoValidator
still need to implement the Validator
interface of org.springframework.validation
Also, do not forget to use @Valid ModeRequestOne
and @Valid ModeRequestTwo
inside the controllers method call.
I do not see a reason why Spring does not filter out all validators which are not applicable to the current entity by default which forces to use things like CompoundValidator described by @Rigg802.
InitBinder
allows you to specify name only which give you some control but not full control over how and when to apply your custom validator. Which from my perspective is not enough.
Another thing you can do is to perform check yourself and add validator to binder only if it is actually necessary, since binder itself has binding context information.
For example if you want to add a new validator which will work with your User object in addition to built-in validators you can write something like this:
@InitBinder
protected void initBinder(WebDataBinder binder) {
Optional.ofNullable(binder.getTarget())
.filter((notNullBinder) -> User.class.equals(notNullBinder.getClass()))
.ifPresent(o -> binder.addValidators(new UserValidator()));
}