【问题标题】:Dynamically create spring beans and alter properties on existing beans动态创建 spring bean 并更改现有 bean 的属性
【发布时间】:2012-08-18 21:31:18
【问题描述】:

我按照http://blog.springsource.com/2007/01/23/dynamic-datasource-routing/的文章成功实现了数据库连接的动态变化。

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

有没有办法在 Spring 上下文中从值列表(即 Year2011DataSource、Year2012DataSource...)创建 bean 并使用刚刚创建的那些 bean 填充数据源 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>

【问题讨论】:

  • 添加了代码的最终版本。感谢大家的帮助。

标签: java spring


【解决方案1】:

我认为自定义 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());
    }
}

【讨论】:

  • 太棒了!现在我只需要弄清楚如何使用对以这种方式创建的 bean 的引用来填充现有“dataSource”bean 上的“targetDataSource”属性。
  • 一切正常,当我稍微清理一下时,会将完成的示例放入原始问题中。再次感谢! :)
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 那些是 Map 的 java.util.Map 吗?因为您只定义/使用一个项目而不是两个(键,值)......
    • 我不能使用这种方法,因为我事先不知道数据源的数量,即在我阅读数据库 url-s 列表之前。 :(
    【解决方案4】:

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

    按照 Biju 的提示,我的一切工作都像这样:

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

    spring 配置中的“Database urls 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")));
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2018-06-18
      • 2012-06-04
      • 2015-12-14
      • 1970-01-01
      • 2015-08-30
      • 2015-01-23
      • 1970-01-01
      • 1970-01-01
      • 2010-10-22
      相关资源
      最近更新 更多