在非托管@Bean上使用@ConfigurationProperties

时间:2019-03-19 19:21:07

标签: java spring spring-boot

我想从@ConfigurationProperties的便利设施中受益,而无需将bean暴露在我的上下文中。这不是@Primaries之类的问题,我根本无法将另一个Datasource暴露给上下文。如何实现以下目标?

@ConfigurationProperties("com.non.exposed.datasource.hikari")
public DataSource privateHikariDatasource() {
    if (Objects.isNull(this.nonExposedDatasource)) {
        this.nonExposedDatasource = this.nonExposedDatasourceProperties.initializeDataSourceBuilder().build();
    }
    return this.nonExposedDatasource;
}

感谢@LppEdd的回答,最终的完美解决方案是:

@Autowired
private Environment environment;

public DataSource privateHikariDatasource() {
    if (Objects.isNull(this.nonExposedDatasource)) {
        this.nonExposedDatasource = bindHikariProperties(this.nonExposedDatasourceProperties.initializeDataSourceBuilder().build());
    }
    return this.nonExposedDatasource;
}

//This does exactly the same as @ConfigurationProperties("com.non.exposed.hikari") but without requiring the exposure of the Datasource in the ctx as @Bean
private <T extends DataSource> T bindHikariProperties(final T instance) {
    return Binder.get(this.environment).bind("com.non.exposed.datasource.hikari", Bindable.ofInstance(instance)).get();
}

然后,您可以使用this.privateHikariDatasource()在内部调用您的bean,以供其他bean使用。 非常感谢@LppEdd!

1 个答案:

答案 0 :(得分:2)

由于该DataSource是类的私有对象,并且包含类可以在Spring上下文中存在,所以可以有一个@ConfigurationProperties

@ConfigurationProperties("com.foo.bar.datasource.hikari")
public class HikariConfiguration { ... }

通过@EnableConfigurationProperties注册后,即可进行自动接线

@EnableConfigurationProperties(HikariConfiguration.class)
@SpringBootApplication
public class Application { ... }

因此可以在包含的类中自动接线

@Component
class MyClass {
   private final HikariConfiguration hikariConfiguration;  
   private DataSource springDatasource;

   MyClass(final HikariConfiguration hikariConfiguration) {
      this.hikariConfiguration = hikariConfiguration;
   }

   ...

   private DataSource privateSingletonDataSource() {
      if (Objects.isNull(this.springDatasource)) {
         this.springDatasource = buildDataSource(this.hikariConfiguration);
      }

      return this.springDatasource;
   }
}

buildDataSource手动构建DataSource实例。
请记住,构建DataSource时需要注意同步。


最终答复是您不能重复使用DataSourceProperties。您甚至无法扩展它来更改属性的前缀。上下文中只能存在一个实例。
您可以做的最好的事情就是模仿Spring的工作。

拥有

com.non.exposed.datasource.hikari.url=testUrl
com.non.exposed.datasource.hikari.username=testUsername
com.non.exposed.datasource.hikari.password=testPassword
...

您可以定义一个新的@ConfigurationProperties

@ConfigurationProperties("com.non.exposed.datasource")
public class NonExposedProperties {
    private final Map<String, String> hikari = new HashMap<>(8);

    public Map<String, String> getHikari() {
        return hikari;
    }
}

然后,将此属性类自动连接到您的@Configuration / @Component类中。
遵循代码中的注释。

@Configuration
public class CustomConfiguration {
    private final NonExposedProperties nonExposedProperties;
    private DataSource dataSource;

    CustomConfiguration(final NonExposedProperties nonExposedProperties) {
        this.nonExposedProperties= nonExposedProperties;
    }

    public DataSource dataSource() {
        if (Objects.isNull(dataSource)) {
            // Create a standalone instance of DataSourceProperties
            final DataSourceProperties dataSourceProperties = new DataSourceProperties();

            // Use the NonExposedProperties "hikari" Map as properties' source. It will be
            // {
            //    url      -> testUrl
            //    username -> testUsername
            //    password -> testPassword
            //    ... other properties
            // }
            final ConfigurationPropertySource source = new MapConfigurationPropertySource(nonExposedProperties.getHikari());

            // Bind those properties to the DataSourceProperties instance
            final BindResult<DataSourceProperties> binded =
                    new Binder(source).bind(
                            ConfigurationPropertyName.EMPTY,
                            Bindable.ofInstance(dataSourceProperties)
                    );

            // Retrieve the binded instance (it's not a new one, it's the same as before)
            dataSource = binded.get().initializeDataSourceBuilder().build();
        }

        // Return the constructed HikariDataSource
        return dataSource;
    }
}