web-dev-qa-db-fra.com

Ajout de plusieurs validateurs en utilisant initBinder

J'ajoute un validateur utilisateur à l'aide de la méthode initBinder:

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

Voici la 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
    }
}

La méthode validate est correctement appelée lors de l'appel de la méthode du contrôleur.

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

Mais en revenant à l'écran de paiement, j'obtiens cette erreur:

Java.lang.IllegalStateException: cible non valide pour Validator [com.validator.UserValidator@710db357]: com.domain.CustomerPayment [customerPaymentId = null] org.springframework.validation.DataBinder.setValidator (DataBinder.Java:476) com.web.UserRegistrationController.initBinder (UserRegistrationController.Java:43) Sun.reflect.NativeMethodAccessorImpl.invoke0 (Méthode native) Sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.Java:39) Sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.Java:25) Java.lang.reflect.Method.invoke (Method.Java:597) org.springframework.web.bind.annotation.support.HandlerMethodInvoker.initBinder (HandlerMethodInvoker.Java:393) org.springframework.web.bind.annotation.support.HandlerMethodInvoker.updateModelAttributes (HandlerMethodInvoker.Java:222) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod (AnnotationMethodHandlerAdapter.Java:429) org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle (AnnotationMethodHandlerAdapter.Java:414)

C'est peut-être parce que je retourne une CustomerPayment et qu'il n'y a pas de validateur défini pour cela.

Je ne parviens pas non plus à ajouter plusieurs validateurs dans la méthode initBinder

Comment puis-je réparer cela?

29
Ravi

Vous devez définir la valeur de l'annotation @InitBinder sur le nom de la commande à valider. Cela indique au printemps à quoi appliquer le classeur; sans cela, Spring essaiera de l'appliquer à tout. C'est pourquoi vous voyez cette exception: Spring tente d'appliquer le classeur - avec votre UserValidator - à un paramètre de type CustomerPayment.

Dans votre cas particulier, il semble que vous ayez besoin de quelque chose comme:

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

Pour répondre à votre deuxième question, comme l'a expliqué Rigg802, Spring ne prend pas en charge l'association de plusieurs validateurs à une seule commande. Vous pouvez cependant définir plusieurs méthodes @InitBinder pour différentes commandes. Ainsi, vous pouvez par exemple mettre les éléments suivants dans un seul contrôleur et valider vos paramètres d'utilisateur et de paiement:

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

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

C'est un peu difficile à faire, 1 contrôleur n'a qu'un validateur sur 1 objet de commande . Vous devez créer un "validateur composite" qui obtiendra tous les validateurs et les exécutera séparément. 

Voici un tutoriel qui explique comment faire: en utilisant plusieurs validateurs

8
Rigg802

Je ne vois pas pourquoi Spring ne filtre pas tous les validateurs qui ne sont pas applicables par défaut à l'entité actuelle, ce qui oblige à utiliser des éléments tels que CompoundValidator décrit par @ Rigg802.

InitBinder vous permet de spécifier un nom uniquement, ce qui vous permet de contrôler, mais pas totalement, comment et quand appliquer votre validateur personnalisé. Ce qui de mon point de vue ne suffit pas.

Vous pouvez également vérifier vous-même et ajouter un validateur au classeur uniquement si cela est réellement nécessaire, car le classeur lui-même contient des informations de contexte contraignantes. 

Par exemple, si vous souhaitez ajouter un nouveau validateur qui fonctionnera avec votre objet Utilisateur en plus des validateurs intégrés, vous pouvez écrire quelque chose comme ceci:

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

}

5

Vous pouvez ajouter plusieurs validateurs en effectuant une itération sur tous les org.springframework.validation.Validator dans un ApplicationContext et configurer ceux appropriés dans @InitBinder pour chaque demande. 

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

Voir mon projet pour des exemples et des repères simples. https://github.com/LyashenkoGS/spring-mvc-and-jms-validation-POC/tree/benchamark

4
Hryhorii Liashenko

Il existe un hack simple, retourne toujours true dans la méthode supports et délègue la vérification de classe à validate. Vous pouvez ensuite ajouter plusieurs validateurs dans la initBinder sans problème.

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

    }

}
2
GMsoF

Plusieurs validateurs sur une commande sont désormais pris en charge avec Spring MVC 4.x. Vous pouvez utiliser ce code de code:

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