为什么@Qualifier不起作用

zhu*_*wei 4 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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

但我显然习惯于@Qualifier识别豆子,例如

@Bean(name = "fooJdbcTemplate")
public JdbcTemplate fooJdbcTemplate(@Qualifier("fooDb") DataSource ds)
Run Code Online (Sandbox Code Playgroud)

为什么不在@Qualifier这里工作?

Mor*_*fic 8

所以我做了一些调试,找到了可以解释发生了什么的东西.在这一点上,我不确定它是否是一个错误(可能是这个),但我还没有找到任何其他文件来澄清这一点.

作为参考,这是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
    ...
Run Code Online (Sandbox Code Playgroud)

发生的情况是,初始化数据源时,spring-boot也会尝试初始化数据库,默认情况下根据文档启用该功能:

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

这发生在以下@PostConstruct部分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();
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它尝试DataSource使用类来执行数据库初始化this.dataSource = this.applicationContext.getBean(DataSource.class);,因为有3个实例而没有主要实例,它会根据预期的行为失败getBean(class)

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

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

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

抛出:
NoSuchBeanDefinitionException - 如果找不到给定类型的bean
==> NoUniqueBeanDefinitionException - 如果找到多个给定类型
的bean BeansException - 如果无法创建bean


所以,最重要的是,这种情况发生在甚至尝试@Qualifier("fooDb")在方法中自动装配你的bean 之前,我相信你有这两个选择,在这两种情况下你的创建@Qualifier时都会考虑到你JdbcTemplate:

  • 如果您需要执行一些脚本来初始化您的数据库,那么使用它@Primary来指示哪些脚本DataSource可用于该任务
  • 否则,您可以通过添加禁用此功能隐含spring.datasource.initialize=false在你application.properties(看到这里,可配置的公共属性的列表)