简体   繁体   English

动态创建spring bean并更改现有bean的属性

[英]Dynamically create spring beans and alter properties on existing beans

I sucessfully managed to implement dynamic changing of database connections by following http://blog.springsource.com/2007/01/23/dynamic-datasource-routing/ article. 我成功地通过http://blog.springsource.com/2007/01/23/dynamic-datasource-routing/文章实现了动态更改数据库连接。

But now the problem is, I have a list of database urls in a configuration file that is managed by a legacy application. 但现在问题是,我在配置文件中有一个由遗留应用程序管理的数据库URL列表。

Is there a way to create beans in that Spring context from a list of values (ie Year2011DataSource, Year2012DataSource,...) and populate map of the dataSource bean with those beans that were just created? 有没有办法从值列表(即Year2011DataSource,Year2012DataSource,...)在Spring上下文中创建bean,并使用刚刚创建的那些bean填充dataSource bean的映射?

<!-- Property file located in the legacy application's folder -->
<context:property-placeholder location="file:///D:/config.properties" />

<!-- Shared data source properties are read from the config.properties file -->
<bean id="parentDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" abstract="true">
    <property name="driverClassName" value="${db.driver}" />
    <property name="username" value="${db.user}" />
    <property name="password" value="${db.password}" />
</bean>

<!-- Database urls by year -->
<bean id="Year2012DataSource" parent="parentDataSource">
    <property name="url" value="jdbc:sqlserver://localhost;databaseName=DbName_v570_2012" />
</bean>
<bean id="Year2011DataSource" parent="parentDataSource">
    <property name="url" value="jdbc:sqlserver://localhost;databaseName=DbName_v570_2011" />
</bean>
<bean id="Year2010DataSource" parent="parentDataSource">
    <property name="url" value="jdbc:sqlserver://localhost;databaseName=DbName_v570_2010" />
</bean>
<!-- ... and so on, these should instead be populated dynamically ... -->

<!-- DbConnectionRoutingDataSource extends AbstractRoutingDataSource -->
<bean id="dataSource" class="someProject.DbConnectionRoutingDataSource">
    <property name="targetDataSources">
        <map key-type="int">
            <entry key="2011" value-ref="Year2011DataSource" />
            <entry key="2010" value-ref="Year2010DataSource" />
            <!-- ... and so on, these also should instead be populated dynamically ... -->
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="Year2012DataSource" />
</bean>

A good fit for this requirement I think is a custom BeanFactoryPostProcessor - read in the legacy configuration and generate the datasources in the custom bean factory post processor: 我认为非常适合这个要求是自定义BeanFactoryPostProcessor - 读取遗留配置并在自定义bean工厂后处理器中生成数据源:

class MyDatasourceRegisteringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        //Read in details from legacy properties.. build custom bean definitions and register with bean factory
        //for each legacy property...
            BeanDefinitionBuilder datasourceDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(BasicDataSource.class).addPropertyValue("url", "jdbc..");
            beanFactory.registerBeanDefinition(datasourceDefinitionBuilder.getBeanDefinition());
    }
}

I can tell you annotation approach. 我可以告诉你注释方法。 I would add urls and configuration in properties file and do something like following : 我会在属性文件中添加URL和配置,并执行以下操作:

@Bean(name="dataSourceMap")
public Map<String, DataSource> dataSourceMap(DataSource dataSource2011, DataSource dataSource2012) {
    // read properties from properties file and create map of datasource

    Map<DataSource> map = new HashMap<>();
    map.put("dataSource2011",dataSource2011);
    map.put("dataSource2012",dataSource2012);
    //map.put("dataSource2013",dataSource2013);
    return map;
}

@Bean(name="dataSource2011",destroyMethod="close")
public DataSource dataSource() {
    // read properties from properties file and create map of 

    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setDriverClassName(driverClassName);
    dataSource.setUrl(url2011);
    dataSource.setUsername(username2011);
    dataSource.setPassword(password2011);               
    return dataSource;
}

@Bean(name="dataSource2012",destroyMethod="close")
public DataSource dataSource() {
    // read properties from properties file and create map of 

    BasicDataSource dataSource = new BasicDataSource();
    dataSource.setDriverClassName(driverClassName);
    dataSource.setUrl(url2012);
    dataSource.setUsername(username2012);
    dataSource.setPassword(password2012);               
    return dataSource;
}

As far as I know, there is no out-of-the-box solution using XML configuration. 据我所知,没有使用XML配置的开箱即用的解决方案。 However, a simple solution to achieve this is described in this answer using FactoryBean abstraction in Spring. 但是,在Spring中使用FactoryBean抽象的答案中描述了实现此目的的简单解决方案。

============================================ ============================================

By following Biju's tip I've got everything working like this: 按照Biju的提示,我得到的一切都是这样的:

============================================ ============================================

"Database urls by year" section in the spring configuration is no more, beans are created in the BeanFactoryPostProcessor. Spring中配置的“数据库url by year”部分已不复存在,Bean是在BeanFactoryPostProcessor中创建的。

"dataSource" bean has it's properties set to dummy data that is replaced in the BeanFactoryPostProcessor: “dataSource”bean将其属性设置为在BeanFactoryPostProcessor中替换的虚拟数据:

<bean id="dataSource" class="someProject.DbConnectionRoutingDataSource">
    <property name="targetDataSources">
        <map key-type="String">
            <!-- Will be filled from the DatasourceRegisteringBeanFactoryPostProcessor -->
        </map>
    </property>
    <property name="defaultTargetDataSource" value="java:jboss/datasources/ExampleDS" />
</bean>

And this is the BeanFactoryPostProcessor implementation: 这是BeanFactoryPostProcessor实现:

@Component
class DatasourceRegisteringBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        InitialContext ic = new InitialContext();

        // read the list of available JNDI connections
        NamingEnumeration<?> list = ic.listBindings(getJndiDSRoot());
        HashSet<String> jndiDataSources = new HashSet<String>();
        while (list.hasMore()) {
            /*... (ommitted for simplicity) ...*/
            connectionsList.put(key, value);
        }            

        BeanDefinitionRegistry factory = (BeanDefinitionRegistry) beanFactory;
        BeanDefinitionBuilder datasourceDefinitionBuilder;

        // Create new beans
        for (Entry<Integer, String> e : connectionsList.entrySet()) {
            datasourceDefinitionBuilder = BeanDefinitionBuilder
                    .childBeanDefinition("parentDataSource")
                    .addPropertyValue("url", e.getValue());

            factory.registerBeanDefinition("Year" + e.getKey() + "DataSource",
                    datasourceDefinitionBuilder.getBeanDefinition());
        }

        // Configure the dataSource bean properties
        MutablePropertyValues mpv = factory.getBeanDefinition("dataSource").getPropertyValues();

        // Here you can set the default dataSource
        mpv.removePropertyValue("defaultTargetDataSource");
        mpv.addPropertyValue("defaultTargetDataSource", 
            new RuntimeBeanReference("Year" + defaultYear + "DataSource"));

        // Set the targetDataSource properties map with the list of connections
        ManagedMap<Integer, RuntimeBeanReference> mm = (ManagedMap<Integer, RuntimeBeanReference>) mpv.getPropertyValue("targetDataSources").getValue();
        mm.clear();

        // Fill the map with bean references to the newly created beans
        for (Entry<Integer, String> e : connectionsList.entrySet()) {
            mm.put(e.getKey(), new RuntimeBeanReference("Year" + e.getKey() + "DataSource")));
        }
    }
}

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM