在非托管@Bean上使用@ConfigurationProperties

问题描述:

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

I would like to benefit from @ConfigurationProperties fantastic facilities without needing to expose the bean into my context. It is not a problem of @Primaries and the like, I simply cannot expose another Datasource into the context. How can I achieve the following?

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


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


Thanks to the answer by @LppEdd, the final perfect solution is:

@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!

Then you can call your bean internally with this.privateHikariDatasource() to be used by your other beans. Great thanks to @LppEdd!

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

Being that this DataSource is private to a class, and that containing class can be/is inside the Spring context, you can have a @ConfigurationProperties class

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

通过@EnableConfigurationProperties注册可以自动接线

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

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

And thus can be autowired in the containing class

@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时需要注意同步.

buildDataSource will manually construct the DataSource instance.
Remember that you need to take care of synchronization when building the DataSource.

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

Final response is that you cannot re-use DataSourceProperties. You can't even extend it to change the properties' prefix. Only a single instance of it can exist inside the context.
The best you can do is mimic what Spring does.

拥有

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类中.
遵循代码中的注释.

Then, autowire this properties class in your @Configuration/@Component class.
Follow in-code comments.

@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;
    }
}