33

I'm working on spring mvc application, where I should aplly validation based on Spring MVC validator. I first step for that I added annotation for class and setup controller and it works fine. And now I need to implement custom validator for perform complex logic, but i want to use existing annotation and just add additional checking.

My User class:

public class User
{
    @NotEmpty
    private String name;

    @NotEmpty
    private String login; // should be unique
}

My validator:

@Component
public class UserValidator implements Validator
{

    @Autowired
    private UserDAO userDAO;

    @Override
    public boolean supports(Class<?> clazz)
    {
        return User.class.equals(clazz) || UsersForm.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors)
    {
        /*
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "NotEmpty.user");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "login", "NotEmpty.user");
        */
        User user = (User) target;
        if (userDAO.getUserByLogin(user.getLogin()) != null) {
            errors.rejectValue("login", "NonUniq.user");
        }
    }
}

My controller:

@Controller
public class UserController
{
    @Autowired
    private UserValidator validator;

    @InitBinder
    protected void initBinder(final WebDataBinder binder)
    {
        binder.setValidator(validator);
    }

    @RequestMapping(value = "/save")
    public ModelAndView save(@Valid @ModelAttribute("user") final User user,
            BindingResult result) throws Exception
    {
        if (result.hasErrors())
        {
            // handle error
        } else
        {
            //save user
        }
    }
}

So, Is it possible to use custom validator and annotation together? And if yes how?

1
  • 1
    What you are trying to do is perfectly legal. What's not working? Commented Aug 1, 2014 at 8:20

3 Answers 3

63

I know this is a kind of old question but, for googlers...

you should use addValidators instead of setValidator. Like following:

@InitBinder
protected void initBinder(final WebDataBinder binder) {
    binder.addValidators(yourCustomValidator, anotherValidatorOfYours);
}

PS: addValidators accepts multiple parameters (ellipsis)

if you checkout the source of org.springframework.validation.DataBinder you will see:

public class DataBinder implements PropertyEditorRegistry, TypeConverter {

    ....

    public void setValidator(Validator validator) {
        assertValidators(validator);
        this.validators.clear();
        this.validators.add(validator);
    }

    public void addValidators(Validator... validators) {
        assertValidators(validators);
        this.validators.addAll(Arrays.asList(validators));
    }

    ....

}

as you see setValidator clears existing (default) validator so @Valid annotation won't work as expected.

Sign up to request clarification or add additional context in comments.

1 Comment

THIS is the answer. thanks! didn't notice the addValidators method
9

If I correctly understand your problem, as soon as you use you custom validator, default validation for @NotEmpty annotation no longer occurs. That is common when using spring : if you override a functionnality given by default, you have to call it explicitely.

You have to generate a LocalValidatorFactoryBean and inject it with your message source (if any). Then you inject that basic validator in you custom validator and delegate annotation validation to it.

Using java configuration it could look like :

@Configuration
public class ValidatorConfig {
    @Autowired
    private MessageSource messageSource;

    @Bean
    public Validator basicValidator() {
        LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
        validator.setValidationMessageSource(messageSource);
        return validator;
    }
}

Then you modify UserValidator to use it :

@Component
public class UserValidator implements Validator
{

    @Autowired
    @Qualifier("basicValidator")
    private Validator basicValidator;

    @Autowired
    private UserDAO userDAO;

    // ...

    @Override
    public void validate(Object target, Errors errors)
    {
        basicValidator.validate(target, errors);
        // eventually stop if any errors
        //  if (errors.hasErrors()) { return; }
        User user = (User) target;
        if (userDAO.getUserByLogin(user.getLogin()) != null) {
            errors.rejectValue("login", "NonUniq.user");
        }
    }
}

3 Comments

Thank you for answer. I guess there are two solution: your and @nicearma. I'm not sure which is more correct, but both are working.
@Vartlok My answer is generic because there are use cases for global validation (multi-parameter) of posted values. But I would try to avoid databases accesses in a validation in controler layer - except if you have no service layer. If you have one, this validation is a business rule and should go there.
But if it have some errors in errors.hasErrors, how we can show it properly on UI ??
7

Well for me you have to delete the

 @InitBinder
protected void initBinder(final WebDataBinder binder)
{
    binder.setValidator(validator);
}

Leave the

@Valid @ModelAttribute("user") final User user,
        BindingResult result

And after in the function make

validator.validate(user,result)

This way you will use the validation basic with the @Valid and after you will put make the more complex validation.

Because with the initBinder you are setting the validation with your complex logic and putting a way the basic logic.

Maybe is wrong, i use always the @Valid without any validator.

4 Comments

But how do you check, for example, that login unique?
And just check, yep, this is working, if pass in method erorr instead of result. As implementation Error i use BindException docs.spring.io/spring/docs/3.1.x/javadoc-api/org/…
Well i think the anwers of @serge-dallesta is better explain and with the good logic, but it is what I was trying to say :)
you do not have to make explicit validation calls in your handler method, just don't SET the validator in InitBinder, but ADD it instead (see @destan's answer below)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.