Search code examples
spring-bootjava-8hibernate-validator

Spring Boot & Hibernate Validation's ConstraintMappingContributor


The hibernate validations documentation describes how to create ConstraintMappingContributors here.

It states:

You then need to specify the fully-qualified class name of the contributor implementation in META-INF/validation.xml, using the property key hibernate.validator.constraint_mapping_contributors. You can specify several contributors by separating them with a comma.

Given I have many of these, what would be the most appropriate way to auto-discover these i.e. via @Component and add them dynamically at runtime to the ConstrainMappingConfiguration during Spring Boot startup.

For example.. if a developer creates a new ConstraintMappingContributor, it should be picked up and added automatically when spring boot starts, requiring no other file changes.


Solution

  • This is what I came up with, seems to be working for me.

    package...
    import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Optional;
    
    @Configuration
    public class ValidationConfiguration {
    
        private final List<ConstraintMappingContributor> contributors;
        public ValidationConfiguration(Optional<List<ConstraintMappingContributor>> contributors) {
            this.contributors = contributors.orElseGet(ArrayList::new);
        }
    
        @Bean
        public LocalValidatorFactoryBean validatorFactory() {
            return new ValidatorFactoryBean(this.contributors);
        }
    }
    
    
    
    package...
    import org.hibernate.validator.HibernateValidatorConfiguration;
    import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
    import org.hibernate.validator.spi.cfg.ConstraintMappingContributor;
    import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
    
    import javax.validation.Configuration;
    import java.util.List;
    
    public class ValidatorFactoryBean extends LocalValidatorFactoryBean {
    
        private final List<ConstraintMappingContributor> contributors;
    
        ValidatorFactoryBean(List<ConstraintMappingContributor> contributors) {
            this.contributors = contributors;
        }
    
        @Override
        protected void postProcessConfiguration(Configuration<?> cfg) {
            if (cfg instanceof HibernateValidatorConfiguration) {
                HibernateValidatorConfiguration configuration = (HibernateValidatorConfiguration) cfg;
                this.contributors.forEach(contributor -> contributor.createConstraintMappings(() -> {
                    DefaultConstraintMapping mapping = new DefaultConstraintMapping();
                    configuration.addMapping(mapping);
                    return mapping;
                }));
            }
        }
    }
    

    I invoke it like this...

    if(SpringValidatorAdapter.class.isInstance(this.validatorFactory)){
        SpringValidatorAdapter.class.cast(this.validatorFactory).validate(entity, errors);
    }