Spring custom validator from interface

When we use layers separation it sometimes become problematic to use custom validator annotation, as to execute it we need service layer not available in data layer. After some digging around I have found a solution, to set validatedBy annotation paramter to interface class instead of implementation class and handle that in the service layer with customized validator factory code.

First we need a class resolving a validator from an interface (if thats interface implementation is available as spring context bean, so annotated with eg. @Component):

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;

/**
 * Similar to SpringConstraintValidatorFactory, but look also for interface implementation to avoid
 * cross dependency between layers because of @Constraint validatedBy
 */
public class InterfaceResolvingSpringConstraintValidatorFactory implements ConstraintValidatorFactory {

    private final Logger logger = LoggerFactory.getLogger(InterfaceResolvingSpringConstraintValidatorFactory.class);
    private final AutowireCapableBeanFactory beanFactory;

    public InterfaceResolvingSpringConstraintValidatorFactory(AutowireCapableBeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @Override
    public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> key) {
        T bean = null;

        try {
            logger.debug("Trying to find a validator bean implementing class " + key.getSimpleName());
            bean = beanFactory.getBean(key);
        } catch (BeansException exc) {
            logger.info("Failed to find a bean of class " + key.getSimpleName());
        }

        if (bean == null) {
            try {
                logger.debug("Creating a new validator bean of class " + key.getSimpleName());
                bean = beanFactory.createBean(key);
            } catch (BeansException exc) {
                logger.info("Failed to create a validator of class " + key.getSimpleName());
            }
        }

        if (bean == null) {
            logger.warn("Failed to get validator of class " + key.getSimpleName());
            //optionally throw some exception here instead
        }

        return bean;
    }

    @Override
    public void releaseInstance(ConstraintValidator<?, ?> constraintValidator) {
        beanFactory.destroyBean(constraintValidator);
    }
}

Seond we use that class in custom LocalValidatorFactoryBean:

import javax.validation.Configuration;

import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Component
public class InterfaceResolvingLocalValidatorFactoryBean extends LocalValidatorFactoryBean {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        super.setApplicationContext(applicationContext);
        this.applicationContext = applicationContext;
    }

    @Override
    protected void postProcessConfiguration(Configuration<?> configuration) {
        InterfaceResolvingSpringConstraintValidatorFactory constraintFactory = new InterfaceResolvingSpringConstraintValidatorFactory(
                applicationContext.getAutowireCapableBeanFactory());
        configuration.constraintValidatorFactory(constraintFactory);
    }
}

Last we need to prepare configuration class, returning MethodValidationPostProcessor with our custom factory set:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class ValidationConfig {

    @Autowired
    private InterfaceResolvingLocalValidatorFactoryBean validatorFactoryBean;

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        processor.setValidator(validatorFactoryBean);
        return processor;
    }
}

Now @Contraint can use validatedBy = ValidationInterface.class instead of validatedBy = ValidationImplementation.class.

Please note that final solution can be also achieved in some other ways, for example with the use of Hibernate ServiceLoader described in a post on in.relation.to.