为什么@Qualifier不起作用

时间:2017-06-26 10:11:25

标签: spring-boot

我使用了spring boot + jdbctemplate,我必须使用多数据源,例如。

@Configuration
public class MultiDBConfig {

    @Bean(name = "fooDb")
    @ConfigurationProperties(prefix = "foo.datasource")
    public DataSource fooDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "fooJdbcTemplate")
    public JdbcTemplate fooJdbcTemplate(@Qualifier("fooDb") DataSource ds) {
        return new JdbcTemplate(ds);
    }

    @Bean(name = "barDb")
    @ConfigurationProperties(prefix = "bar.datasource")
    public DataSource barDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "barJdbcTemplate")
    public JdbcTemplate barJdbcTemplate(@Qualifier("barDb") DataSource ds) {
        return new JdbcTemplate(ds);
    }

}

启动我的应用程序时,它失败并且有以下错误信息

Parameter 0 of method fooJdbcTemplate in com.example.multidatasourcedemo.MultiDBConfig required a single bean, but 3 were found:
    - fooDb: defined by method 'fooDataSource' in class path resource [com/example/multidatasourcedemo/MultiDBConfig.class]
    - barDb: defined by method 'barDataSource' in class path resource [com/example/multidatasourcedemo/MultiDBConfig.class]
    - testDb: defined by method 'testDataSource' in class path resource [com/example/multidatasourcedemo/MultiDBConfig.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

但我显然使用@Qualifier来识别bean,例如

@Bean(name = "fooJdbcTemplate")
public JdbcTemplate fooJdbcTemplate(@Qualifier("fooDb") DataSource ds)

为什么@Qualifier在这里工作?

2 个答案:

答案 0 :(得分:8)

所以我做了一些调试,找到了一些可以解释发生了什么的东西。此时我不确定它是否是一个错误(可能是this one),但我也找不到任何其他文档来澄清这一点。

作为参考,这是spring-boot 1.5.4。

我从日志开始,你可以在下面找到一段摘录,更具体地说是关于DataSourceInitializer.init的行(下面是==>开头):

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected single matching bean but found 3: fooDb,barDb,testDb
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1041) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:345) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1090) ~[spring-context-4.3.9.RELEASE.jar:4.3.9.RELEASE]
==> at org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer.init(DataSourceInitializer.java:77) ~[spring-boot-autoconfigure-1.5.4.RELEASE.jar:1.5.4.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_45]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_45]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_45]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:366) ~[spring-beans-4.3.9.RELEASE.jar:4.3.9.RELEASE]
    at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:311) ~[spring-beans-4.3.9.RELEASE
    ...

当初始化数据源时,弹出启动会尝试初始化数据库,默认情况下根据the docs启用该功能:

  

Spring JDBC具有DataSource初始化程序功能。 Spring Boot默认启用它,并从标准位置schema.sqldata.sql(在类路径的根目录中)加载SQL。

这发生在@PostConstruct的{​​{1}}部分:

org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer

正如您所看到的,它尝试使用类@PostConstruct public void init() { if (!this.properties.isInitialize()) { logger.debug("Initialization disabled (not running DDL scripts)"); return; } if (this.applicationContext.getBeanNamesForType(DataSource.class, false, false).length > 0) { ==> this.dataSource = this.applicationContext.getBean(DataSource.class); } if (this.dataSource == null) { logger.debug("No DataSource found so not initializing"); return; } runSchemaScripts(); } DataSource执行数据库初始化,并且因为有3个实例而没有主要实例,所以它根据{{3}失败}

  

< T> T getBean (Class< T> requiredType)抛出BeansException   
返回唯一匹配给定对象类型的bean实例(如果有)。   
此方法进入ListableBeanFactory按类型查找区域,但也可以根据给定类型的名称转换为常规的按名称查找。对于跨Bean集的更广泛的检索操作,请使用ListableBeanFactory和/或BeanFactoryUtils。   

参数:   
requiredType - 类型bean必须匹配;可以是接口或超类。不允许使用null。   

返回:   
一个匹配所需类型的单个bean的实例   

<强>抛出:   
NoSuchBeanDefinitionException - 如果没有找到给定类型的bean   
==&GT; NoUniqueBeanDefinitionException - 如果找到多个给定类型的bean   
BeansException - 如果无法创建bean

所以,最重要的是,这种情况发生在甚至尝试在方法中自动装配你的this.dataSource = this.applicationContext.getBean(DataSource.class); bean之前,我相信你有这两个选择,在这两种情况下你的@Qualifier("fooDb")都会被采用在创建@Qualifier时考虑到了:

  • 如果您需要执行一些脚本来初始化您的数据库,请使用JdbcTemplate来指明哪个@Primary可用于该任务
  • 否则,您可以在DataSource中添加spring.datasource.initialize=false来禁用此隐式功能(请参阅expected behaviour of getBean(class)可配置的常用属性列表)

答案 1 :(得分:0)

这可能是由一些不同的事情引起的。在我的情况下,我有以下情况:

  • 两个数据源bean在两个Java类中配置,但都给出了特定的Bean ID
  • 正在注入数据源的一个位置,但使用限定符正确注释
  • 正确排除DataSourceAutoConfiguration
  • 的SpringBootApplication

然而,该错误原来是:第二个类被注释为SpringBootApplication并且正在启动...在日志中丢失。 因此,如果其他一切看起来都是正确的:检查是否有其他意外的SpringBootApplication正在启动。