Java 如何在 Spring Boot 中以编程方式创建 bean?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25160221/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I create beans programmatically in Spring Boot?
提问by Matt Raible
I have an app that has a number of datasource settings listed in application.properties. I have a @ConfigurationProperties
class that loads up these settings. Now I want to take the values from this ConfigurationProperties
class and use them to create DataSource beans on-the-fly. I've tried using @PostConstruct
and implementing BeanFactoryPostProcessor
. However, with BeanFactoryPostProcessor
, the processing seems to happen to early - before my ConfigurationProperties
class has been populated. How can I read properties and create DataSource
beans on the fly with Spring Boot?
我有一个应用程序,其中包含许多列在 application.properties 中的数据源设置。我有一个@ConfigurationProperties
加载这些设置的类。现在我想从这个ConfigurationProperties
类中获取值并使用它们来即时创建 DataSource bean。我试过使用@PostConstruct
和实现BeanFactoryPostProcessor
. 但是,使用BeanFactoryPostProcessor
,处理似乎发生得早 - 在我的ConfigurationProperties
班级被填充之前。如何DataSource
使用 Spring Boot读取属性并动态创建bean?
Here's what my application.properties looks like:
这是我的 application.properties 的样子:
ds.clients[0]=client1|jdbc:db2://server/client1
ds.clients[1]=client2,client3|jdbc:db2://server/client2
ds.clients[2]=client4|jdbc:db2://server/client4
ds.clients[3]=client5|jdbc:db2://server/client5
And my ConfigurationProperties class:
还有我的 ConfigurationProperties 类:
@Component
@ConfigurationProperties(prefix = "ds")
public class DataSourceSettings {
public static Map<String, String> CLIENT_DATASOURCES = new LinkedHashMap<>();
private List<String> clients = new ArrayList<>();
public List<String> getClients() {
return clients;
}
public void setClients(List<String> clients) {
this.clients = clients;
}
@PostConstruct
public void configure() {
for (String client : clients) {
// extract client name
String[] parts = client.split("\|");
String clientName = parts[0];
String url = parts[1];
// client to datasource mapping
String dsName = url.substring(url.lastIndexOf("/") + 1);
if (clientName.contains(",")) {
// multiple clients with same datasource
String[] clientList = clientName.split(",");
for (String c : clientList) {
CLIENT_DATASOURCES.put(c, dsName);
}
} else {
CLIENT_DATASOURCES.put(clientName, dsName);
}
}
}
At the end of this @PostConstruct
method, I'd like to create a BasicDataSource
with these settings and add it to the ApplicationContext. However, if I try to do this by implement BeanFactoryPostProcessor
and implementing postProcessBeanFactory
, the clients
property is null, as is the CLIENT_DATASOURCES
that I've populated with @PostConstruct
.
在此@PostConstruct
方法结束时,我想BasicDataSource
使用这些设置创建一个并将其添加到 ApplicationContext。但是,如果我尝试通过实施BeanFactoryPostProcessor
和实施来做到这一点postProcessBeanFactory
,则该clients
属性为空,就像CLIENT_DATASOURCES
我填充的@PostConstruct
.
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("clients: " + CLIENT_DATASOURCES);
}
What's the best way to create datasources on-the-fly with Spring Boot?
使用 Spring Boot 即时创建数据源的最佳方法是什么?
采纳答案by Stephane Nicoll
How about creating your beans and ask Spring Boot to inject values into it?
如何创建您的 bean 并要求 Spring Boot 向其中注入值?
Something like
就像是
@Bean
@ConfigurationProperties("ds.client1")
public DataSource dataSourceClient1() {
DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("ds.client2")
public DataSource dataSourceClient2() {
DataSourceBuilder.create().build();
}
Then, any setting in the ds.client1
namespace belongs to the first data source (i.e. ds.client1.password
is the data source password for that DataSource
).
然后,ds.client1
命名空间中的任何设置都属于第一个数据源(即ds.client1.password
是那个的数据源密码DataSource
)。
But maybe you don't know how much data sources you'll have? This is getting more complicated, especially if you need to inject those dynamic data sources in other objects. If you only need to lookup them by name, you could register them yourself as singletons. Here is an example that works
但也许您不知道您将拥有多少数据源?这变得越来越复杂,尤其是当您需要将这些动态数据源注入其他对象时。如果您只需要按名称查找它们,您可以将它们自己注册为单身人士。这是一个有效的例子
@ConfigurationProperties(prefix = "ds")
public class DataSourceSettings implements BeanFactoryAware {
private List<String> clients = new ArrayList<>();
private BeanFactory beanFactory;
public List<String> getClients() {
return clients;
}
public void setClients(List<String> clients) {
this.clients = clients;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@PostConstruct
public void configure() {
Map<String, String> clientDataSources = new HashMap<String, String>();
for (String client : clients) {
// extract client name
String[] parts = client.split("\|");
String clientName = parts[0];
String url = parts[1];
// client to datasource mapping
String dsName = url.substring(url.lastIndexOf("/") + 1);
if (clientName.contains(",")) {
// multiple clients with same datasource
String[] clientList = clientName.split(",");
for (String c : clientList) {
clientDataSources.put(c, url);
}
}
else {
clientDataSources.put(clientName, url);
}
}
Assert.state(beanFactory instanceof ConfigurableBeanFactory, "wrong bean factory type");
ConfigurableBeanFactory configurableBeanFactory = (ConfigurableBeanFactory) beanFactory;
for (Map.Entry<String, String> entry : clientDataSources.entrySet()) {
DataSource dataSource = createDataSource(entry.getValue());
configurableBeanFactory.registerSingleton(entry.getKey(), dataSource);
}
}
private DataSource createDataSource(String url) {
return DataSourceBuilder.create().url(url).build();
}
}
Note that those beans are onlyavailable by bean name lookup. Let me know if that works out for you.
请注意,这些 bean只能通过 bean 名称查找来使用。如果这对你有用,请告诉我。
回答by Lari Hotari
I created an example project on github to demonstrate your usecase.
我在 github 上创建了一个示例项目来演示您的用例。
https://github.com/lhotari/dynamic-datasources
https://github.com/lhotari/dynamic-datasources
I implemented a ImportBeanDefinitionRegistrarto add the beans. You can get a hold of the configuration by implementing EnvironmentAware. There might be other ways to achieve your goal, but this was the way I used in GspAutoConfigurationto register beans dynamicly. GspAutoConfiguration makes Grails GSP available in Spring Boot applications.
我实现了一个ImportBeanDefinitionRegistrar来添加 bean。您可以通过实现EnvironmentAware 来掌握配置。可能还有其他方法可以实现您的目标,但这是我在GspAutoConfiguration 中用于动态注册 bean 的方式。GspAutoConfiguration 使 Grails GSP 在 Spring Boot 应用程序中可用。
Here's the relevant configuration class in the dynamic-datasource sample: https://github.com/lhotari/dynamic-datasources/blob/master/src/main/groovy/sample/DynamicDataSourcesConfiguration.java
下面是动态数据源示例中的相关配置类:https: //github.com/lhotari/dynamic-datasources/blob/master/src/main/groovy/sample/DynamicDataSourcesConfiguration.java
package sample;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.bind.PropertiesConfigurationFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import org.springframework.validation.BindException;
@Configuration
public class DynamicDataSourcesConfiguration implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private ConfigurableEnvironment environment;
private static Map<String, Object> defaultDsProperties = new HashMap<String, Object>() {
{
put("suppressClose", true);
put("username", "sa");
put("password", "");
put("driverClassName", "org.h2.Driver");
}
};
@Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment)environment;
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
DataSourceSettings settings = resolveSettings();
for (Entry<String, String> entry : settings.clientDataSources().entrySet()) {
createDsBean(registry, entry.getKey(), entry.getValue());
}
}
private void createDsBean(BeanDefinitionRegistry registry, String beanName, String jdbcUrl) {
GenericBeanDefinition beanDefinition = createBeanDefinition(SingleConnectionDataSource.class);
beanDefinition.getPropertyValues().addPropertyValues(defaultDsProperties).addPropertyValue("url", jdbcUrl);
registry.registerBeanDefinition(beanName, beanDefinition);
}
private GenericBeanDefinition createBeanDefinition(Class<?> beanClass) {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_NO);
return beanDefinition;
}
private DataSourceSettings resolveSettings() {
DataSourceSettings settings = new DataSourceSettings();
PropertiesConfigurationFactory<Object> factory = new PropertiesConfigurationFactory<Object>(settings);
factory.setTargetName("ds");
factory.setPropertySources(environment.getPropertySources());
factory.setConversionService(environment.getConversionService());
try {
factory.bindPropertiesToTarget();
}
catch (BindException ex) {
throw new FatalBeanException("Could not bind DataSourceSettings properties", ex);
}
return settings;
}
}