spring-projects / spring-boot

Spring Boot helps you to create Spring-powered, production-grade applications and services with absolute minimum fuss.
https://spring.io/projects/spring-boot
Apache License 2.0
75.27k stars 40.7k forks source link

Conditional Bean Creation Not Working For JSR303Validator #8548

Closed pluttrell closed 7 years ago

pluttrell commented 7 years ago

When using the following Gradle dependencies:

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.SR5"
    }
}

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-web'
    compile 'org.springframework.boot:spring-boot-starter-actuator'
    compile 'org.springframework.cloud:spring-cloud-starter-zipkin'
    compile 'org.springframework.boot:spring-boot-starter-data-rest'
    compile 'org.springframework.data:spring-data-keyvalue'
    compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
    compile 'com.google.guava:guava:20.0'
    compile 'io.springfox:springfox-swagger2:2.6.1'
    compile 'io.springfox:springfox-data-rest:2.6.1'
    compile 'io.springfox:springfox-bean-validators:2.6.1'
    compile 'io.springfox:springfox-swagger-ui:2.6.1'
    testCompile 'org.springframework.boot:spring-boot-starter-test'
}

If you want to enable universal JSR 303 Bean Validations on Spring Data Rest as shown here, with this class:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
import org.springframework.validation.Validator;

@Configuration
public class ValidatingRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {
    @Autowired
    private Validator validator;
    @Override
    public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
        validatingListener.addValidator("beforeCreate", validator);
        validatingListener.addValidator("beforeSave", validator);
    }
}

You'll get the following error on startup:

Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2017-03-08 23:07:47.303 ERROR [-,,,] 6070 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Field validator in com.exp.auth.api.ValidatingRepositoryRestConfigurerAdapter required a single bean, but 2 were found:
    - jsr303Validator: defined by method 'jsr303Validator' in class path resource [org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.class]
    - mvcValidator: defined by method 'mvcValidator' in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]

Action:

Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed

Process finished with exit code 1

The cause appears to be that there are multiple Validator beans defined. If I look at org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration I see the following method within:

@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ConditionalOnMissingBean
public static Validator jsr303Validator() {
    LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
    MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
    factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
    return factoryBean;
}

Shouldn't the @ConditionalOnMissingBean annotation mean that this bean doesn't get created given that there is already another one named mvcValidator created?

Annotating the injection @Lazy does not resolve the issue as noted here.

Annotating the injection either @Qualifier("jsr303Validator") or @Qualifier("mvcValidator") does resolve the problem. But which one should we use?

wilkinsona commented 7 years ago

Duplicates #8495

snicoll commented 7 years ago

What bugs me in the example is that you're mentioning JSR 303 Validator (javax.validation.Validator) and that's not what you're injecting. On the other hand the Data Rest contract does not seem to allow you to pass such object. That actually makes the case for #8495 :(