Добавление нескольких валидаторов с помощью 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);