Saltar al contenido

Agregar ConstraintValidator personalizado para @Future y LocalDate a un proyecto Spring Boot

Solución:

Estoy de acuerdo con Miloš en que usar el META-INF/validation.xml es probablemente la forma más limpia y fácil, pero si realmente desea configurarlo en un Spring @Confguration clase, entonces es posible y aquí hay una forma en que puede hacerlo.

La belleza de Spring Boot es que realiza muchas configuraciones en su nombre para que no tenga que preocuparse por ello. Sin embargo, esto también puede causar problemas cuando desea configurar algo específicamente usted mismo y no es muy obvio cómo hacerlo.

Así que ayer me puse a intentar añadir un CustomerValidator por @Past y LocalDate utilizando el ConstraintDefinitionContributor mecanismo que sugiere Hardy (y al que se hace referencia en la documentación de Hibernate).

Lo simple fue escribir la clase de implementación para hacer la validación, que para mis propósitos altamente específicos consistió en:

public class PastValidator implements ConstraintValidator 

    @Override
    public void initialize(Past constraintAnnotation) 

    @Override
    public boolean isValid(LocalDate value, ConstraintValidatorContext context) 
        return null != value && value.isBefore(LocalDate.now());
    

Luego me volví perezoso y solo creé una instancia @Bean en mi configuración, solo por la remota posibilidad de que una de las clases de configuración automática de Spring lo recoja y lo conecte al validador de Hibernate. Esta fue una posibilidad bastante remota, dada la documentación disponible (o la falta de ella) y lo que Hardy y otros habían dicho, y no valió la pena.

Así que encendí un depurador y trabajé al revés desde que se lanzó la excepción org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree que me decía que no podía encontrar un validador para @Past y LocalDate.

Mirando la jerarquía de tipos del ConstraintValidatorFactory Descubrí que había dos clases de implementación en mi aplicación Spring MVC: SpringConstraintValidatorFactory y SpringWebConstraintValidatorFactory que ambos simplemente intentan obtener un bean del contexto de la clase correcta. Esto me dijo que tengo que tener mi validador registrado con Spring BeanFactory, sin embargo, cuando puse un punto de interrupción en esto, pero no fue golpeado por mi PastValidator, lo que significaba que Hibernate no sabía que debería incluso solicitar esta clase.

Esto tenía sentido: no había ninguna ConstraintDefinitionContributor en cualquier lugar para decirle a Hibernate que necesitaba pedirle a Spring una instancia de PastValidator. El ejemplo en la documentación en http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html_single/#section-constraint-definition-contributor sugiere que necesitaría acceso a un HibernateValidatorConfiguration así que solo necesitaba encontrar dónde Spring estaba configurando.

Después de investigar un poco, descubrí que todo estaba sucediendo en Spring’s LocalValidatorFactoryBean clase, específicamente en su afterPropertiesSet() método. De su javadoc:

/*
 * This is the central class for @code javax.validation (JSR-303) setup in a Spring
 * application context: It bootstraps a @code javax.validation.ValidationFactory and
 * exposes it through the Spring @link org.springframework.validation.Validator interface
 * as well as through the JSR-303 @link javax.validation.Validator interface and the
 * @link javax.validation.ValidatorFactory interface itself.
 */

Básicamente, si no configura su propio Validador, aquí es donde Spring intenta hacerlo por usted, y en true Estilo de primavera, proporciona un método de extensión útil para que pueda dejar que haga su configuración y luego agregar la suya propia a la mezcla.

Entonces mi solución fue simplemente extender el LocalValidatorFactoryBean para poder registrar el mío ConstraintDefinitionContributor instancias:

import java.util.ArrayList;
import java.util.List;
import javax.validation.Configuration;
import org.hibernate.validator.internal.engine.ConfigurationImpl;
import org.hibernate.validator.spi.constraintdefinition.ConstraintDefinitionContributor;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

public class ConstraintContributingValidatorFactoryBean extends LocalValidatorFactoryBean 

    private List contributors = new ArrayList<>();

    public void addConstraintDefinitionContributor(ConstraintDefinitionContributor contributor) 
        contributors.add(contributor);
    

    @Override
    protected void postProcessConfiguration(Configuration configuration) 
        if(configuration instanceof ConfigurationImpl) 
            ConfigurationImpl config = ConfigurationImpl.class.cast(configuration);
            for(ConstraintDefinitionContributor contributor : contributors)
                config.addConstraintDefinitionContributor(contributor);
        
    

y luego instanciar y configurar esto en mi configuración de Spring:

    @Bean
    public ConstraintContributingValidatorFactoryBean validatorFactory() 
        ConstraintContributingValidatorFactoryBean validatorFactory = new ConstraintContributingValidatorFactoryBean();
        validatorFactory.addConstraintDefinitionContributor(new ConstraintDefinitionContributor() 
            @Override
            public void collectConstraintDefinitions(ConstraintDefinitionBuilder builder) 
                    builder.constraint( Past.class )
                            .includeExistingValidators( true )
                            .validatedBy( PastValidator.class );
            
        );
        return validatorFactory;
    

y para completar, aquí también he creado una instancia de PastValidator frijol:

    @Bean
    public PastValidator pastValidator() 
        return new PastValidator();
    

Otras cositas elásticas

Noté en mi depuración que debido a que tengo una aplicación Spring MVC bastante grande, estaba viendo dos instancias de SpringConstraintValidatorFactory y uno de SpringWebConstraintValidatorFactory. Descubrí que este último nunca se usó durante la validación, así que lo ignoré por el momento.

Spring también tiene un mecanismo para decidir qué implementación de ValidatorFactory utilizar, por lo que es posible que no utilice su ConstraintContributingValidatorFactoryBean y en su lugar use otra cosa (lo siento, encontré la clase en la que hizo esto ayer, pero no pude encontrarla de nuevo hoy, aunque solo pasé unos 2 minutos buscando). Si está utilizando Spring MVC de alguna manera no trivial, es probable que ya haya tenido que escribir su propia clase de configuración, como esta, que implementa WebMvcConfigurer donde puede conectar explícitamente en su Validator frijol:

public static class MvcConfigurer implements WebMvcConfigurer 

    @Autowired
    private ConstraintContributingValidatorFactoryBean validatorFactory;

    @Override
    public Validator getValidator() 
        return validatorFactory;
    

    // ...
    // lots of other overridden methods
    // ...

Esto está mal

Como se ha señalado, debe tener cuidado al aplicar un @Past validación a un LocalDate porque no hay información sobre la zona horaria. Sin embargo, si está usando LocalDate debido a que todo se ejecutará en la misma zona horaria, o si deliberadamente desea ignorar las zonas horarias, o simplemente no le importa, entonces esto está bien para usted.

Deberá agregar su propio validador al META-INF/validation.xml archivo, así:



    
        
            package.to.LocalDateFutureValidator
        
    

Para más detalles, consulte la documentación oficial.

En caso de que alguien tenga un problema con el validation.xml acercarse y se está poniendo Cannot find the declaration of element constraint-mappings error, como hice, tuve que hacer las siguientes modificaciones. Espero que esto le ahorre a alguien el tiempo que dediqué a resolver esto.

META-INF/validation.xml:


    META-INF/validation/past.xml

META-INF/validation/past.xml:


    
        
            your.package.PastConstraintValidator
        
    

Comentarios y valoraciones de la guía

Si haces scroll puedes encontrar las crónicas de otros creadores, tú de igual forma puedes mostrar el tuyo si dominas el tema.

¡Haz clic para puntuar esta entrada!
(Votos: 2 Promedio: 4.5)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *