Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection in JSR-303 Constraint Validator with Spring fails

I have the same problem as here and here but couldn't find a solution yet.

So my sample test project will show the whole relevant configuration and code:

Constraint annotation:

@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FooValidator.class)
public @interface FooValid {

    String message();

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Annotated PoJo:

public class Foo {

    @FooValid(message = "Test failed")
    private Integer test;
    [...]
}

Annotated Service with @Validated:

@Service
@Validated
public class FooService {

    private final Test test;

    @Autowired
    public FooService(final Test test) {
        this.test = test;
    }

    public void foo(@Valid final Foo foo) {
        this.test.test(foo);
    }
}

JSR-303 ConstraintValidator:

public class FooValidator implements ConstraintValidator<FooValid, Integer> {

    @Autowired
    private ValidationService validationService;

    @Override
    public void initialize(final FooValid constraintAnnotation) {
        // TODO Auto-generated method stub

    }

    @Override
    public boolean isValid(final Integer value, final ConstraintValidatorContext context) {
        // this.validationService is always NULL!
        Assert.notNull(this.validationService, "the validationService must not be null");
        return false;
    }

}

Injected ValidationService:

@Service
public class ValidationService {

    public void test(final Foo foo) {
        System.out.println(foo);
    }
}

Spring boot application and configuration:

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application {

    public static void main(final String[] args) {
        final ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
        final FooService service = context.getBean(FooService.class);
        service.foo(new Foo());
    }

    @Bean
    public static LocalValidatorFactoryBean validatorFactory() {
        return new LocalValidatorFactoryBean();
    }

    @Bean
    public static MethodValidationPostProcessor validationPostProcessor() {
        return new MethodValidationPostProcessor();
    }

}

relevant maven pom:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.1.9.RELEASE</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
    </dependency>
</dependencies>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <start-class>demo.Application</start-class>
    <java.version>1.7</java.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

I'm using the LocalValidatorFactoryBean with the default SpringConstraintValidatorFactory. But why the dependency injection is not working in the ConstraintValidator and the ValidationService could not be autowired?

By the way if I don't use @Validated at the service, inject in opposite the spring or javax Validator interface and call manually "validator.validate" the dependency injection will work. But I don't want to call the validate method in every service manually.

Many thanks for help :)

like image 928
oil ID Avatar asked Dec 17 '14 13:12

oil ID


People also ask

Which is the default Validator of spring?

Spring MVC Framework supports JSR-303 specs by default and all we need is to add JSR-303 and it's implementation dependencies in Spring MVC application.

How does a spring Validator work?

Spring features a Validator interface that you can use to validate objects. The Validator interface works using an Errors object so that while validating, validators can report validation failures to the Errors object.

What is the use of Hibernate Validator dependency?

Hibernate Validator allows to express and validate application constraints. The default metadata source are annotations, with the ability to override and extend through the use of XML. It is not tied to a specific application tier or programming model and is available for both server and client application programming.

Is Hibernate Validator thread safe?

Validating constraints In the setUp() method, a Validator instance is retrieved from the ValidatorFactory . Validator instances are thread-safe and may be reused multiple times. The validate() method returns a set of ConstraintViolation instances, which you can iterate in order to see which validation errors occurred.


1 Answers

I have fought the same problem in Spring Boot environment and I found out that Hibernate internal implementation got in instead of the configured Spring's one. When the application started, debugger caught a line with the Spring's factory but later in runtime there was Hibernate's one. After some debugging, I came to the conclusion that MethodValidationPostProcessor got the internal one. Therefore I configured it as follows:

@Bean
public Validator validator() {
    return new LocalValidatorFactoryBean();
}

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor(Validator validator) {
    MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
    methodValidationPostProcessor.setValidator(validator);
    return methodValidationPostProcessor;
}

Note the setter for validator - it did the job.

like image 192
Ladislav Gazo Avatar answered Oct 19 '22 21:10

Ladislav Gazo