Добавление нескольких валидаторов с помощью initBinder

Я добавляю валидатор пользователя, используя initBinder метод:

@InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(new UserValidator());
    }

Здесь UserValidator

public class UserValidator implements Validator {

    public boolean supports(Class clazz) {
        return User.class.equals(clazz);
    }

    public void validate(Object target, Errors errors) {
        User u = (User) target;

        // more code here
    }
}

validate метод вызывается правильно во время вызова метода контроллера.

@RequestMapping(value = "/makePayment", method = RequestMethod.POST)
public String saveUserInformation(@Valid User user, BindingResult result, Model model){

    // saving User here

    // Preparing CustomerPayment object for the payment page.
    CustomerPayment customerPayment = new CustomerPayment();
    customerPayment.setPackageTb(packageTb);
    model.addAttribute(customerPayment);
    logger.debug("Redirecting to Payment page.");

    return "registration/payment";
}

Но, возвращаясь к экрану оплаты, я получаю эту ошибку:

java.lang.IllegalStateException: недопустимая цель для Validator [com.validator.UserValidator@710db357]: com.domain.CustomerPayment[ customerPaymentId=null ] org.springframework.validation.DataBinder.setValidator(DataBinder.javab).47.UserRegistrationController.initBinder(UserRegistrationController.java:43) sun.reflect.NativeMethodAccessorImpl.invoke0(собственный метод) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.jetetIhoImphoImphoIdImping.java.java):.lang.reflect.Method.invoke(Method.java:597) org.springframework.web.bind.annotation.support.HandlerMethodInvoker.initBinder(HandlerMethodInvoker.java:393) org.springframework.wenoHort.su, tion.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:414)

Это может быть потому, что я возвращаю CustomerPayment и для этого не определен валидатор.

Я также не могу добавить несколько валидаторов в initBinder метод.

Как я могу это исправить?

9 ответов

Вам необходимо установить значение @InitBinder аннотация к имени команды, которую вы хотите проверить. Это говорит Spring, к чему применять связующее; без этого Spring попытается применить его ко всему. Вот почему вы видите это исключение: Spring пытается применить связующее UserValidator - параметру типа CustomerPayment,

В вашем конкретном случае, похоже, вам нужно что-то вроде:

@InitBinder("user")
protected void initBinder(WebDataBinder binder) {
    binder.setValidator(new UserValidator());
}

На ваш второй вопрос, как объяснил Rigg802, Spring не поддерживает присоединение нескольких валидаторов к одной команде. Вы можете, однако, определить несколько @InitBinder методы для разных команд. Так, например, вы можете поместить следующее в один контроллер и проверить параметры пользователя и платежа:

@InitBinder("user")
protected void initUserBinder(WebDataBinder binder) {
    binder.setValidator(new UserValidator());
}

@InitBinder("payment")
protected void initPaymentBinder(WebDataBinder binder) {
    binder.setValidator(new CustomerPaymentValidator());
}

Это немного сложно сделать, 1 контроллер имеет только 1 валидатор на 1 объект команды. вам нужно создать "составной валидатор", который будет получать все валидаторы и запускать их отдельно.

Вот учебник, который объясняет, как это сделать: использование нескольких валидаторов

Вы можете добавить несколько валидаторов, перебирая все org.springframework.validation.Validator в ApplicationContext и устанавливая подходящие в @InitBinder для каждого запроса.

@InitBinder
public void setUpValidators(WebDataBinder webDataBinder) {
    for (Validator validator : validators) {
        if (validator.supports(webDataBinder.getTarget().getClass())
                && !validator.getClass().getName().contains("org.springframework"))
            webDataBinder.addValidators(validator);
    }
}

Смотрите мой проект для примеров и простых тестов. https://github.com/LyashenkoGS/spring-mvc-and-jms-validation-POC/tree/benchamark

Я не вижу причины, по которой Spring не отфильтровывает все валидаторы, которые по умолчанию не применимы к текущей сущности, что вынуждает использовать такие вещи, как CompoundValidator, описанный @Rigg802.

InitBinder позволяет вам указать только имя, которое дает вам некоторый контроль, но не полный контроль над тем, как и когда применять ваш собственный валидатор. Что с моей точки зрения недостаточно.

Другая вещь, которую вы можете сделать, это выполнить проверку самостоятельно и добавить валидатор в связыватель, только если это действительно необходимо, так как сам связыватель имеет информацию о контексте связывания.

Например, если вы хотите добавить новый валидатор, который будет работать с вашим объектом User в дополнение к встроенным валидаторам, вы можете написать что-то вроде этого:

@InitBinder
protected void initBinder(WebDataBinder binder) {
  Optional.ofNullable(binder.getTarget())
      .filter((notNullBinder) -> User.class.equals(notNullBinder.getClass()))
      .ifPresent(o -> binder.addValidators(new UserValidator()));

}

Есть простой взлом, всегда возвращаюсь true в supports метод и делегировать проверку класса validate, Затем вы можете добавить несколько валидаторов в initBinder без проблем.

@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");
        }

    }

}

Самый безопасный способ - добавить универсальный валидатор, обрабатывающий этот контроллер:

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.setValidator(new GenericControllerOneValidator());
    }

Затем в универсальном валидаторе вы можете поддерживать несколько моделей тела запроса и на основе экземпляра объекта вы можете вызвать соответствующий валидатор:

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

Затем вы добавляете свои собственные проверки внутри для каждой реализации валидатора модели. ModeRequestOneValidator а также ModeRequestTwoValidator по-прежнему необходимо реализовать Validator интерфейс org.springframework.validationТакже не забывайте использовать @Valid ModeRequestOne а также @Valid ModeRequestTwo внутри вызова метода контроллеров.

Spring Valve 4.x теперь поддерживает несколько валидаторов для одной команды. Вы можете использовать этот фрагмент кода:

@InitBinder
protected void initBinder(WebDataBinder binder) {
    binder.addValidators(new UserValidator(), new CustomerPaymentValidator());
}

Одно дополнение к ответу Аннабель:

Если у контроллера есть этот параметр метода, и вы хотите его конкретно проверить

       @RequestMapping(value = "/users", method = RequestMethod.POST)
 public String findUsers(UserRequest request){..}

Тогда привязка должна быть в нижнем регистре имени класса (но только первая буква, а не все остальное)

      @InitBinder("userRequest")
protected void initUserBinder(WebDataBinder binder) {
    binder.setValidator(new YourValidator());
}

Объявить запрос как

(... , Model model,HttpServletRequest request)

и изменить

model.addAttribute(customerPayment);

в

request.setAttribute("customerPayment",customerPayment);
Другие вопросы по тегам