Spring動態註冊bean實現動態多數據源 – JAVA編程語言程序開發技術文章

項目原來已經實現瞭多數據源配置,實現方式為在beans.xml文件中直接配置多個數據源bean,然後在使用數據源時通過HotSwappableTargetSource動態切換數據源(詳細內容請Google)。可領導不滿意,要求隻在屬性文件中配置相應的連接信息,並要求動態數據源除配置的屬性外,其他屬性都繼承系統默認數據源(DataSource)的屬性。然後給出的屬性文件中數據源的格式為:

[plain] 
#連接數據庫地址,該地址可動態添加,“link.”之後,“.jdbc”之前的名稱為數據庫連接池的名稱,其餘連接池屬性如果不寫,將自動繼承Hummer的數據庫連接池配置 
link.eagle2.business.jdbc.jdbcUrl=jdbc:oracle:thin:@192.168.0.155:1521:orcl 
link.eagle2.business.jdbc.user=eagle2 
link.eagle2.business.jdbc.password=eagle2_password 
link.eagle2.interface.jdbc.jdbcUrl=jdbc:oracle:thin:@192.168.0.155:1521:interface 
link.eagle2.interface.jdbc.user=interface 
link.eagle2.interface.jdbc.password=interface22 
link.eagle2.ods.jdbc.jdbcUrl=jdbc:oracle:thin:@192.168.0.10:1521:sifen 
link.eagle2.ods.jdbc.user=honghe 
link.eagle2.ods.jdbc.password=honghe_pwd 

為實現這個要求,我經過google搜索,寫代碼嘗試,最後終於較好的實現瞭該功能,結果記錄如下:
實現一個實現ApplicationContextAware和ApplicationListener接口的類DynamicDataSourceC3p0,實現ApplicationContextAware是為瞭得到ApplicationContext,實現瞭ApplicationListener是為瞭配置spring的加載事件。
類的實現代碼如下:
[java] 
import java.io.IOException; 
import java.util.HashMap; 
import java.util.Iterator; 
import java.util.Map; 
import java.util.regex.Matcher; 
import java.util.regex.Pattern; 
 
import org.springframework.beans.BeansException; 
import org.springframework.beans.factory.config.BeanDefinition; 
import org.springframework.beans.factory.support.ChildBeanDefinition; 
import org.springframework.beans.factory.support.DefaultListableBeanFactory; 
import org.springframework.context.ApplicationContext; 
import org.springframework.context.ApplicationContextAware; 
import org.springframework.context.ApplicationEvent; 
import org.springframework.context.ApplicationListener; 
import org.springframework.context.event.ContextRefreshedEvent; 
import org.springframework.core.io.support.ResourcePropertySource; 
 
import com.mchange.v2.c3p0.ComboPooledDataSource; 
 
public class DynamicDataSourceC3p0 implements ApplicationContextAware,ApplicationListener { 
 
    private ApplicationContext app; 
    @Override 
    public void setApplicationContext(ApplicationContext app) 
            throws BeansException { 
        this.app = app; 
    } 
    public void onApplicationEvent(ApplicationEvent event) {   
        //如果是容器刷新事件   
        if(event instanceof ContextRefreshedEvent ){//如果是容器關閉事件   
            try { 
                regDynamicBean(); 
            } catch (IOException e) { 
                e.printStackTrace(); 
            } 
            //System.out.println(event.getClass().getSimpleName()+" 事件已發生!"); 
        } 
    }   
 
    private void regDynamicBean() throws IOException{ 
        // 解析屬性文件,得到數據源Map 
        Map<String, DataSourceInfo> mapCustom = parsePropertiesFile("hummer.properties"); 
        // 把數據源bean註冊到容器中 
        addSourceBeanToApp(mapCustom); 
         
    } 
    /**
     * 功能說明:根據DataSource創建bean並註冊到容器中
     * @param acf
     * @param mapCustom
     */ 
    private void addSourceBeanToApp(Map<String, DataSourceInfo> mapCustom) { 
        DefaultListableBeanFactory acf = (DefaultListableBeanFactory) app.getAutowireCapableBeanFactory(); 
        BeanDefinition beanDefinition; 
        Iterator<String> iter = mapCustom.keySet().iterator(); 
        while(iter.hasNext()){ 
            String beanKey = iter.next(); 
            // 得到Bean定義,並添加到容器中 
            beanDefinition = new ChildBeanDefinition("dataSource"); 
            // 註意:必須先註冊到容器中,再得到Bean進行修改,否則數據源屬性不能有效修改 
            acf.registerBeanDefinition(beanKey, beanDefinition); 
            // 再得到數據源Bean定義,並修改連接相關的屬性 
            ComboPooledDataSource cpds = (ComboPooledDataSource)app.getBean( beanKey);; 
 
            cpds.setJdbcUrl(mapCustom.get(beanKey).connUrl); 
            cpds.setUser(mapCustom.get(beanKey).userName); 
            cpds.setPassword(mapCustom.get(beanKey).password); 
        } 
    } 
    /**
     * 功能說明:解析屬性文件,得到數據源Map
     * @return
     * @throws IOException
     */ 
    private Map<String, DataSourceInfo> parsePropertiesFile(String fileName) throws IOException { 
        // 屬性文件 
        ResourcePropertySource props =  new ResourcePropertySource(fileName); 
         
        Matcher matcher; 
        Pattern pattern = Pattern.compile("^link\\.(eagle2\\.\\w+)\\.jdbc\\.(jdbcUrl|user|password)$"); 
         
        Map<String, DataSourceInfo> mapDataSource = new HashMap<String,DataSourceInfo>(); 
        // 根據配置文件解析數據源 
        for(String keyProp : props.getPropertyNames()) 
        { 
            matcher = pattern.matcher(keyProp); 
            if(matcher.find()){ 
                String dsName = matcher.group(1); 
                String dsPropName = matcher.group(2); 
                DataSourceInfo dsi; 
                 
                if(mapDataSource.containsKey(dsName)){ 
                    dsi = mapDataSource.get(dsName); 
                } 
                else{ 
                    dsi = new DataSourceInfo(); 
                } 
                // 根據屬性名給數據源屬性賦值 
                if("jdbcUrl".equals(dsPropName)){ 
                    dsi.connUrl = (String)props.getProperty(keyProp); 
                }else if("user".equals(dsPropName)){ 
                    dsi.userName = (String)props.getProperty(keyProp); 
                }else if("password".equals(dsPropName)){ 
                    dsi.password = (String)props.getProperty(keyProp); 
                } 
                mapDataSource.put(dsName, dsi); 
            } 
        } 
        return mapDataSource; 
    } 
     
    private class DataSourceInfo{ 
        public String connUrl; 
        public String userName; 
        public String password; 
         
        public String toString(){ 
             
            return "(JcbcUrl:"+connUrl+", user:"+userName+", password:"+password+")"; 
        } 
    } 

然後在beans.xml文件中添加配置
"ApplicationEventListener" class="com.dfsoft.hummer.domain.common.DynamicDataSourceC3p0" />
讓spring在加載時調用DynamicDataSourceC3p0中的onApplicationEvent方法實現動態註冊數據源。而原來實現多數據源動態切換的地方完全不用修改。
附加說明:
1、 做完上面的工作之後,在beans.xml中去掉原來的多數據源配置(已經沒有用瞭)。
2、 acf.registerBeanDefinition不用判斷容器中是否已經有相應名字的bean,spring會在該名稱的bean存在時覆蓋,不存在時添加。

發佈留言