首页 文章

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

提问于
浏览
22

我有一个应用程序,其中包含application.properties中列出的许多数据源设置 . 我有一个加载这些设置的 @ConfigurationProperties 类 . 现在我想从这个 ConfigurationProperties 类获取值,并使用它们即时创建DataSource bean . 我尝试过使用 @PostConstruct 并实现 BeanFactoryPostProcessor . 但是,使用 BeanFactoryPostProcessor ,处理似乎很早就发生了 - 在我的 ConfigurationProperties 类已经填充之前 . 如何使用Spring Boot快速读取属性并创建 DataSource beans?

这是我的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

我的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);
            }
        }
    }

在这个 @PostConstruct 方法的最后,我想用这些设置创建一个 BasicDataSource 并将其添加到ApplicationContext . 但是,如果我尝试通过实现 BeanFactoryPostProcessor 并实现 postProcessBeanFactory 来执行此操作,则 clients 属性为null,因为我填充了 @PostConstructCLIENT_DATASOURCES .

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    System.out.println("clients: " + CLIENT_DATASOURCES);
}

使用Spring Boot动态创建数据源的最佳方法是什么?

2 回答

  • 10

    如何创建bean并请求Boot向其中注入值?

    就像是

    @Bean
    @ConfigurationProperties("ds.client1")
    public DataSource dataSource() {
        DataSourceBuilder.create().build();
    }
    
    @Bean
    @ConfigurationProperties("ds.client2")
    public DataSource dataSource() {
        DataSourceBuilder.create().build();
    }
    

    然后, ds.client1 命名空间中的任何设置都属于第一个数据源(即 ds.client1.password 是该 DataSource 的数据源密码) .

    但也许您不知道您将拥有多少数据源?这变得越来越复杂,特别是如果您需要在其他对象中注入这些动态数据源 . 如果您只需要按名称查找它们,您可以将它们自己注册为单身 . 这是一个有效的例子

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

    请注意,通过bean名称查找可以使用这些bean only . 如果这对您有用,请告诉我 .

  • 19

    我在github上创建了一个示例项目来演示你的用例 .

    https://github.com/lhotari/dynamic-datasources

    我实现了ImportBeanDefinitionRegistrar来添加bean . 您可以通过实现EnvironmentAware来获取配置 . 可能还有其他方法可以实现您的目标,但这是我在GspAutoConfiguration中用于动态注册bean的方式 . GspAutoConfiguration使Grails GSP可用于Spring Boot应用程序 .

    这是dynamic-datasource示例中的相关配置类: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;
        }
    
    }
    

相关问题