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

时间:2012-08-23 11:19:40

标签: java spring

我成功地通过关注http://blog.springsource.com/2007/01/23/dynamic-datasource-routing/文章来实现动态更改数据库连接。

但现在问题是,我在配置文件中有一个由遗留应用程序管理的数据库URL列表。

有没有办法从值列表(即Year2011DataSource,Year2012DataSource,...)在Spring上下文中创建bean,并使用刚刚创建的bean填充dataSource bean的map?

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

4 个答案:

答案 0 :(得分:8)

非常适合这个要求我认为是自定义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());
    }
}

答案 1 :(得分:1)

我可以告诉你注释方法。我会在属性文件中添加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;
}

答案 2 :(得分:1)

据我所知,没有使用XML配置的开箱即用解决方案。但是,this answer在Spring中使用FactoryBean抽象描述了实现此目的的简单解决方案。

答案 3 :(得分:1)

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

通过遵循Biju的提示,我得到的一切都是这样的:

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

弹出配置中的“数据库url by year”部分已不复存在,bean在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>

这是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")));
        }
    }
}