Adding multiple validators using initBinder

后端 未结 8 1447
傲寒
傲寒 2020-12-04 18:04

I\'m adding a user validator using the initBinder method:

@InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.setVa         


        
相关标签:
8条回答
  • 2020-12-04 18:33

    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());
    }
    
    0 讨论(0)
  • 2020-12-04 18:35

    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());
    }
    
    0 讨论(0)
  • 2020-12-04 18:38

    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

    0 讨论(0)
  • 2020-12-04 18:41

    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");
            }
    
        }
    
    }
    
    0 讨论(0)
  • 2020-12-04 18:42

    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.

    0 讨论(0)
  • 2020-12-04 18:44

    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()));
    

    }

    0 讨论(0)
提交回复
热议问题