简体   繁体   English

如何在 Spring Boot 中以编程方式创建 bean?

[英]How do I create beans programmatically in Spring Boot?

I have an app that has a number of datasource settings listed in application.properties.我有一个应用程序,其中包含许多列在 application.properties 中的数据源设置。 I have a @ConfigurationProperties class that loads up these settings.我有一个加载这些设置的@ConfigurationProperties类。 Now I want to take the values from this ConfigurationProperties class and use them to create DataSource beans on-the-fly.现在我想从这个ConfigurationProperties类中获取值并使用它们来即时创建数据源 bean。 I've tried using @PostConstruct and implementing BeanFactoryPostProcessor .我试过使用@PostConstruct并实现BeanFactoryPostProcessor However, with BeanFactoryPostProcessor , the processing seems to happen to early - before my ConfigurationProperties class has been populated.但是,对于BeanFactoryPostProcessor ,处理似乎发生得早——在我的ConfigurationProperties类被填充之前。 How can I read properties and create DataSource beans on the fly with Spring Boot?如何使用 Spring Boot 即时读取属性和创建DataSource 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.在这个@PostConstruct方法的最后,我想用这些设置创建一个BasicDataSource并将它添加到 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 .但是,如果我尝试通过实现BeanFactoryPostProcessor并实现postProcessBeanFactory来做到这一点,则clients属性为 null,我使用@PostConstruct填充的CLIENT_DATASOURCES如此。

@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 即时创建数据源的最佳方法是什么?

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 (ie 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 only available by bean name lookup.请注意,这些 bean只能通过 bean 名称查找来使用。 Let me know if that works out for you.如果这对你有用,请告诉我。

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 ImportBeanDefinitionRegistrar to add the beans.我实现了一个ImportBeanDefinitionRegistrar来添加 bean。 You can get a hold of the configuration by implementing EnvironmentAware .您可以通过实现EnvironmentAware 来掌握配置。 There might be other ways to achieve your goal, but this was the way I used in GspAutoConfiguration to register beans dynamicly.可能还有其他方法可以实现您的目标,但这是我在GspAutoConfiguration 中用于动态注册 bean 的方式。 GspAutoConfiguration makes Grails GSP available in Spring Boot applications. 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;
    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在 Spring boot 控制台应用程序中将 spring bean 注入非托管 bean? - How do I inject spring beans into non-managed beans in a Spring boot console application? 如何根据参数创建spring boot bean - How to create spring boot beans based on parameter Spring JavaConfig我如何引用为创建新bean而定义的bean - Spring JavaConfig how do I refer to beans that I have defined to create new beans Spring:如何在 Spring 中创建类似的 bean 动态启动? - Spring : How to create similar beans in Spring Boot dynamically? 我在哪里将我的XML bean放在Spring Boot应用程序中? - Where do I put my XML beans in a Spring Boot application? 如何在不使用 bean 或注释的情况下手动初始化 Spring Boot 存储库? (科特林) - How do I manually initialize a Spring Boot repository without using beans or annotations? (Kotlin) 如何找到给定的Spring Boot类/包的bean列表? - How do I find a list of beans for a given Spring Boot class/package? Spring Boot Application如何在没有@Configuration类的情况下创建bean - How does a Spring Boot Application create beans without @Configuration class 如何在运行时为 Spring Boot 中的不同配置文件创建多个 bean? - How to create multiple beans at runtime for different profiles in Spring Boot? Spring 引导:如何更新 Bean? - Spring Boot: How to Update Beans?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM