Spring Boot用新的前缀扩展了现有的属性类

时间:2018-06-21 01:21:42

标签: java spring-boot

除了标准的"app.datasource.alternate"DataSourceProperties)之外,我还希望设置一些自定义数据源属性("spring.datasource")。

  

这两个数据源没有竞争(即app.datasource.alternate属性不能替代spring.datasource属性),应该在默认的应用程序属性中并存。

我希望这样的事情可以工作:

@ConfigurationProperties("app.datasource.alternate")
public class AlternateDataSourceProperties extends DataSourceProperties {}

但是,当我在Yaml中定义属性时:

app:
  datasource:
    alternate: 
      schema-username: TEST

并运行我的测试:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = NONE)
@EnableConfigurationProperties({AlternateDataSourceProperties.class})
public class AlternateDataSourcePropertiesTest {

    @Autowired
    // @Qualifier("alternateDataSourceProperties")
    private AlternateDataSourceProperties props;

    @Test
    public void propertiesAreInjected() {
        assertThat(props.getSchemaUsername()).isEqualTo("TEST");
    }
}

尝试在NoUniqueBeanDefinitionException中创建datasource bean时,程序失败,并显示DataSourceConfiguration.Tomcat

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Tomcat.class]: Unsatisfied dependency expressed through method 'dataSource' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.autoconfigure.jdbc.DataSourceProperties' available: expected single matching bean but found 2: app.datasource.alternate-com.example.config.AlternateDataSourceProperties,spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:749)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:467)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.autoconfigure.jdbc.DataSourceProperties' available: \
  expected single matching bean but found 2: \
  app.datasource.alternate-com.example.config.AlternateDataSourceProperties \
  ,spring.datasource-org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:173)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1116)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:835)
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741)
    ... 66 more

我不想重写一个已经存在的整个属性类,我只需要在不同前缀下的另外一组属性即可。在Spring Boot中有可能吗?

我正在使用Spring Boot 1.5.X

更新

从@Prashant的回答中得到启发,我尝试创建一个附加的配置文件并向其注入以下内容:@PropertySource("classpath:config/alternate-ds.yml")

# alternate-ds.yml
app:
  datasource:
    alternate:
      schema-username: TEST

# AlternateDataSourceProperties.java
@ConfigurationProperties("app.datasource.alternate")
@PropertySource("classpath:config/alternate-ds.yml")
public class AlternateDataSourceProperties extends DataSourceProperties {}

我仍然得到NoUniqueBeanDefinitionException,因为现在仍然存在竞争的bean资源。有没有一种方法可以指定不应该自动接线的Bean?

2 个答案:

答案 0 :(得分:0)

我的方法是:

    public class WebApplication { 
       private static final String CONFIGURATION_FILE_NAME_KEY = "spring.config.name";
       private static final String CONFIGURATION_FILE_NAME_VALUE = new StringJoiner(",")
            .add("application")
            .add("aurora_web")
            .toString();
       private static final String CONFIGURATION_FILE_NAME_PROPERTY = MessageFormat.format("{0}:{1}", CONFIGURATION_FILE_NAME_KEY, CONFIGURATION_FILE_NAME_VALUE);

       public static void main(String[] args) {
        new SpringApplicationBuilder(WebApplication.class)
                .properties(CONFIGURATION_FILE_NAME_PROPERTY)
                .build()
                .run(args);
    }

Spring允许通过spring.config.name配置属性文件名。这可以用来允许使用自定义文件名代替默认的application.[property|yml]。您可以使用覆盖数据源配置的值来创建自己的属性文件,Spring会为您选择该文件。您可以使用@Value()访问这些属性,或者如果您已从spring的属性中覆盖默认键值,则将从该文件中选择新的键值。 您可以参考following official documentation

答案 1 :(得分:0)

  

根据@Prashant的回答,我提出了一个解决方案。该解决方案具有一定的侵入性,因为它重新定义了现有的属性bean,因此可能很危险,但是@SpringBootTest似乎可以接受。

     

对我来说,最佳解决方案是以某种方式声明自定义bean不应该由自动装配拾取,我尝试将autowire = Autowire.NO参数添加到自定义属性集的@Bean批注中,但这仍然给出NoUniqueBeanDefinitionException

问题在于,由于我在应用程序上下文中添加了DataSourceProperties类的其他实例,因此在库代码中引用spring时不知道要注入哪个DataSourceProperties。我想出了一个权宜之计,就是将原始属性定义重新创建为以@Bean注释的自定义@Primary,这样我的自定义DataSourceProperties Bean就不会产生歧义。

当我需要特殊的数据源属性时,我使用@Qualifier("alternateDataSourceProperties")来引用正确的集合。

Configuration类如下:

@Configuration
public class AlternateDataSourcePropertiesConfiguration {
   @Bean
    @ConfigurationProperties("app.datasource.alternate")
    public DataSourceProperties alternateDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource")
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }
}

我对它们进行了如下测试:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = NONE, properties={"app.datasource.alternate.schema-username=TEST",
"app.datasource.alternate.username=SOMETHING"})
@ActiveProfiles("test") // there is a default test datasource configured in application-test.properties so I can test that primary configuration is unaffected. i.e. I have a H2 datasource configured here with username 'sa'
public class AlternateDataSourcePropertiesConfigurationTest {

    @Autowired
    @Qualifier("alternateDataSourceProperties")
    private DataSourceProperties alternateDataSourceProperties;

    @Autowired
    private DataSourceProperties primaryDataSourceProperties;

    @Test
    public void propertiesAreInjected() {
        assertThat(alternateDataSourceProperties.getSchemaUsername()).isEqualTo("TEST");
    }

    @Test
    public void propertiesDontOverridePrimary() {
        assertThat(alternateDataSourceProperties.getUsername()).isEqualTo("SOMETHING");
        assertThat(primaryDataSourceProperties.getUsername()).isEqualTo("sa");
    }
}