javaspringjavax-validation

How to retrieve the javax ConstraintValidator handled by Spring?


As seen in this Baeldung example, it is possible to write plain old javax.validation objects and Spring is able to manage them. For example, it is possible to perform field injection in a validator without annotating it as a component:

public class MyValidator implements ConstraintValidator<MyAnnotation, String> {

    @Autowired
    private MyBean bean;

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
       return bean.getValidElements().contains(value);
    }

}

Yet, MyValidator is not a Spring bean: a beanFactory.getBean(MyValidator.class) (or an applicationContext.getBean(MyValidator.class)) will throw

org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'my.package.MyValidator' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:351)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)

This can be fixed adding a @Component annotation on MyValidator, but maybe it is cleaner to not annotate them as components: I have several validators that extends a common abstract class, so I'm planning to annotate that class in this way, but I feel it would be more maintenable to just have a beanFactory.getBean(MyValidator.class) of sorts and keep each validator as plain as possible.

Is there a way to retrieve the validators without annotating them as components, as Spring already manage them somehow? A minimal example can be found here.


Solution

  • The Validation API has support to have dependencies injected into the ConstraintValidator based on the annotation metadata. This has nothing to do with Spring but is supported by the Validation API.

    To support the injection there is the [javax.jakarta].validation.ConstraintValidatorFactory interface. Spring has 2 implementations for this, but commonly used is the SpringWebConstraintValidatorFactory.

    What happens is that when validation is requested based on the annotations present the Validation API will call ConstraintValidatorFactory.create to get an instance of the needed validator(s).

    Spring will construct an instance on the fly and if there are @Autowired or @Inject fields (or constructors), Spring will happily inject the dependencies.

    The created validators are NOT Spring Managed beans. The lifecycle of those validators is totally in control of the Validation API and not Spring. As such they are also not retrievable from the ApplicationContext, unless you explictly make them beans.

    To make them beans you can do 1 of several things.

    1. Annotate them with @Component.
    2. Create an @Configuration class with @Bean methods
    3. Create an XML file to register them as beans
    4. Add an @ComponentScan(includeFilter = @Filter(type=ASSIGNABLE_TYPE, classes = ConstraintValidator.class).

    Either will (or can) result in your validators being Spring managed.